前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS小技能: 网络加载中处理、接口暂无数据处理、全局监听用户点击事件

iOS小技能: 网络加载中处理、接口暂无数据处理、全局监听用户点击事件

作者头像
公众号iOS逆向
发布2022-08-22 12:38:02
7780
发布2022-08-22 12:38:02
举报
文章被收录于专栏:iOS逆向与安全

引言

在日常开发中经常涉及数据列表的查询,处理服务侧无数据返回的情况或者网络异常的手段是iOS必备小技能。

如果是iOS新手,可以先看第三章节的预备知识。

I 网络加载中处理

本文以SVProgressHUD框架为例子进行讲解

1.1 UIWindowLevel

UIWindowLevel级别的高低顺序从小到大为Normal < StatusBar < Alert,Level高的将排在所有Level比他低的层级的前面。

代码语言:javascript
复制
    UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
    UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
    UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar API_UNAVAILABLE(tvos);

SVProgressHUD 相关方法

代码语言:javascript
复制
    [SVProgressHUD setMaxSupportedWindowLevel:UIWindowLevelStatusBar];

1.2 图层样式

默认图层样式显示的时候,允许用户交互适合大部分场景。但是像注册、登录、切换账号的场景建议使用不允许交互样式。

请求包含这些关键字的API时,建议不允许交互 :CreateAddUpdateDeleteBindSubmitSaveLoginChangeSetEditModifyCancelSendCheckAudit

代码语言:javascript
复制
typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
    SVProgressHUDMaskTypeNone = 1,  // default mask type, allow user interactions while HUD is displayed
    SVProgressHUDMaskTypeClear,     // don't allow user interactions with background objects
    SVProgressHUDMaskTypeBlack,     // don't allow user interactions with background objects and dim the UI in the back of the HUD (as seen in iOS 7 and above)
    SVProgressHUDMaskTypeGradient,  // don't allow user interactions with background objects and dim the UI with a a-la UIAlertView background gradient (as seen in iOS 6)
    SVProgressHUDMaskTypeCustom     // don't allow user interactions with background objects and dim the UI in the back of the HUD with a custom color
};

  1. 加载框展示自定义图片:setInfoImage:
代码语言:javascript
复制
// 设置加载信息
    NSString *path = [[NSBundle mainBundle] pathForResource:@"加载" ofType:@"gif"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    UIImage *image = [UIImage sd_imageWithGIFData:data];
    [SVProgressHUD setInfoImage:image];
    [SVProgressHUD setImageViewSize:CGSizeMake(60, 60)];
    [SVProgressHUD showInfoWithStatus:@""];
  1. 使用默认样式展示相关文字信息:showWithStatus
代码语言:javascript
复制
    [SVProgressHUD setBackgroundColor: [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.30]];
    [SVProgressHUD setForegroundColor: UIColor.whiteColor];
    [SVProgressHUD setMaximumDismissTimeInterval:0.5];//
    [SVProgressHUD showWithStatus:@"加载中..."];

1.3 灵活控制是否允许交互

思路:通过请求方法入口进行控制是否允许交互 实现步骤:

  1. 配置不允许交互的URL
  2. 展示加载框时,判断URL是否允许交互,即在调用showLoadingDataGif时判断
代码语言:javascript
复制
//
//  ERPLoading.m
//  retail
//
//  Created by mac on 2022/8/10.
//  Copyright © 2022 QCT. All rights reserved.
//

#import "ERPLoading.h"

@interface ERPLoading ()

/**
 不允许交互的URL
 */
@property (strong, nonatomic) NSMutableArray *urls4no_allow_user_interactions;

@end

@implementation ERPLoading
HSSingletonM(ERPLoading);
//配置不允许交互的URL
- (NSMutableArray *)urls4no_allow_user_interactions{
    
    if(_urls4no_allow_user_interactions == nil){
        
        _urls4no_allow_user_interactions = [NSMutableArray array];
        //登录 /api/xxx/xxx
        [_urls4no_allow_user_interactions addObject:URL_Login];
        //切换门店
        [_urls4no_allow_user_interactions addObject:k_API_Users_ChangeStore];
        
    }
    
    return _urls4no_allow_user_interactions;
}

/**
 判断是否允许交互:
 URL参数是绝对路径,urls4no_allow_user_interactions数组中的元素是相对路径 因此先对URL去除域名部分,只留相对路径再进行内容的比较。

 */
+(BOOL)isAllow_user_interactions:(NSString*)url{
    NSString *separate =@"/api/";// 由于API格式统一是 /api/xxx/xxx,因此可以简单的采用字符串分割即可。
    url = [url componentsSeparatedByString:separate].lastObject;
    url= FMSTR(@"%@%@",separate,url);// 这里可以拼接上分隔符之后直接调用数组的containsObject方法进行比较,也可以采用谓词技术进行匹配https://blog.csdn.net/z929118967/article/details/74066170
    NSLog(@"relativeString:%@",url);
    
    if([[ERPLoading shareERPLoading].urls4no_allow_user_interactions containsObject:url])
        
    {
        [self no_allow_user_interactions];
        return NO;
    }
    [self allow_user_interactions];
    return YES;
}

//allow user interactions
+(void)allow_user_interactions{
    [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone];
}
//don't allow user interactions
+(void)no_allow_user_interactions{
    [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeCustom];
}
// 加载的gif
+ (void)showLoadingDataGifWith:(NSString*)url{
    //1. 判断是否允许交互
    [self isAllow_user_interactions:url];
// 2. 设置加载信息
    NSString *path = [[NSBundle mainBundle] pathForResource:@"加载" ofType:@"gif"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    UIImage *image = [UIImage sd_imageWithGIFData:data];
    [SVProgressHUD setInfoImage:image];
    [SVProgressHUD setImageViewSize:CGSizeMake(60, 60)];
    [SVProgressHUD setBackgroundColor:[UIColor clearColor]];
//    [SVProgressHUD setBackgroundColor: [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.30]];
//    [SVProgressHUD setForegroundColor: UIColor.whiteColor];

    // 设置最小显示时长。default is 0 seconds
//    [SVProgressHUD setMinimumDismissTimeInterval:0.4];
    [SVProgressHUD setBackgroundLayerColor:STModalWindowDefaultBackgroundColor];
    [SVProgressHUD showInfoWithStatus:@""];
}
// 隐藏gif
+ (void)hiddenLoadingDataGif{
    [SVProgressHUD dismiss];
}
@end

1.4 点击空白处隐藏提示框

实现思路: 自定义应用对象UIApplication来全局监听用户点击事件

实现步骤:

  1. 自定义应用,并重写sendEvent:
代码语言:javascript
复制
@implementation ERPApplication

- (void)sendEvent:(UIEvent *)event
{
    //如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者。
    [super sendEvent:event];//这里一定不能漏掉
    ///点击空白处隐藏提示框:
    [ERPLoading setupTouch4hiddenLoadingDataGif:event];
    
}


  1. 在main方法创建应用对象的传递自定义的应用对象类型。
代码语言:javascript
复制
 int UIApplicationMain(int argc, char * _Nullable *argv, NSString *principalClassName, NSString *delegateClassName);
// Creates the application object and the application delegate and sets up the event cycle.
  1. 点击空白处隐藏提示框:监听点击事件,如果是允许交互的样式SVProgressHUDMaskTypeNone则调用dismiss。
代码语言:javascript
复制
+ (void)setupTouch4hiddenLoadingDataGif:(UIEvent *)event{
    

    //当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象;一根手指对应一个UItouch对象。
    //UItouch保存着跟手指相关的信息,比如触摸的位置、时间、阶段。
    //根据touches中UITouch的个数可以判断出是单点触摸还是多点触
    NSSet *allTouches = [event allTouches];

    if ([allTouches count] <= 0)

    {
        return ;
        
    }
    
    UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;

    if (phase != UITouchPhaseBegan){
        
        return ;
    }

    
//点击空白处隐藏提示框:点击空白处隐藏提示框: 监听点击事件,如果是允许交互的样式SVProgressHUDMaskTypeNone则调用dismiss。
    SVProgressHUD *sharedView =[self sharedView];
    
    NSLog(@"%@:defaultMaskType: %lu send event",UIApplication.sharedApplication,(unsigned long)sharedView.defaultMaskType);
    
    
    if(sharedView.defaultMaskType == SVProgressHUDMaskTypeNone ){
        [self hiddenLoadingDataGif];
    }


}

+(SVProgressHUD*)sharedView{
    
    return  [SVProgressHUD performSelector:@selector(sharedView)];
    
    
}

II 暂无数据处理

网络请求失败,业务逻辑错误,返回数据为空都是需要处理界面的显示,推荐使用暂无数据进行提示。

在这里插入图片描述

2.1 用法

代码语言:javascript
复制
        if (weakSelf.viewModel.listDataArray.count == 0) {
            [weakSelf.viewModel.ShowNoviewSubject sendNext:QCTLocal(CRM_nodata_Info)];
            
        }else{
            [weakSelf.viewModel.hidenNoviewSubject sendNext:nil];
            
        }

2.2 核心实现

V层初始化暂无数据视图:将视图添加到tableView,这样可以不影响下拉刷新和上拉加载

代码语言:javascript
复制
- (CRMNoDatatView *)NoView{
    
    if (nil == _NoView) {
        
        CRMNoDatatView *tmpView = [[CRMNoDatatView alloc]init];
        
        _NoView = tmpView;
        [self.tableView addSubview:_NoView];
        
        __weak __typeof__(self) weakSelf = self;
        
        
        [_NoView mas_makeConstraints:^(MASConstraintMaker *make) {
            
            
            make.centerY.equalTo(weakSelf.tableView.mas_centerY).offset(kAdjustRatio(k_noteViewH));
            make.width.equalTo(weakSelf);
            
            
            
            make.left.right.bottom.equalTo(weakSelf.tableView);//tableView


            
        }];
        
        
        
    }
    return _NoView;
}



- (void)ShowNoview:(NSString *)title img:(NSString*)imgName
{

    
    self.NoView.title = title;
    
    self.NoView.imgName = imgName;
    
    [self.tableView bringSubviewToFront:self.NoView];
    
}

V层监听C层的事件

代码语言:javascript
复制
    [self.viewModel.hidenNoviewSubject subscribeNext:^(id  _Nullable x) {
        weakSelf.NoView.hidden = YES;
    }];
    
    [self.viewModel.ShowNoviewSubject subscribeNext:^(id  _Nullable x) {
        weakSelf.NoView.hidden = NO;
        
        [weakSelf ShowNoview:x img:@"img_kongbai_zanwu"];

        
        
    }];

暂无数据视图的实现

代码语言:javascript
复制
// 显示暂无数据图片
- (UIImageView *)imageV{
    if (nil == _imageV) {
        UIImageView *tmpView = [[UIImageView alloc]init];
        _imageV = tmpView;
        
        _imageV.contentMode = UIViewContentModeScaleAspectFit;

        _imageV.image = [UIImage imageNamed:@"icon_wushuju"];

        [self addSubview:_imageV];
        __weak __typeof__(self) weakSelf = self;

        
        [_imageV mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(weakSelf);
            make.centerY.equalTo(weakSelf).offset(-kAdjustRatio(35));

            make.left.equalTo(weakSelf).offset(kAdjustRatio(33));
            
            make.right.equalTo(weakSelf).offset(kAdjustRatio(-33));
            
            

        }];
    }
    return _imageV;
}


//显示暂无数据文本
- (UILabel *)label{
    if (nil == _label) {
        UILabel *tmpView = [[UILabel alloc]init];
        _label = tmpView;
        [self addSubview:_label];
        __weak __typeof__(self) weakSelf = self;

        
        [_label mas_makeConstraints:^(MASConstraintMaker *make) {
            
            make.centerX.equalTo(weakSelf);
            make.top.equalTo(weakSelf.imageV.mas_bottom).offset(kAdjustRatio(22));
            
        
        _label.textAlignment = NSTextAlignmentCenter;

        
        _label.font = kPingFangFont(15);
        _label.textColor = rgb(51,51,51);

    }
    return _label;
}




// 更新图片数据
-(void)setImgName:(NSString *)imgName{
    _imgName = imgName;
    
    
    if (imgName.length<=0) {
        return;
    }
    [self.imageV setImage:[UIImage imageNamed:imgName]];
    
    
        self.reloadbtnView.hidden = !self.isShowReloadBtn;
//    }
}

- (void)setTitle:(NSString *)title{
    _title = title;
    
    self.label.text = title;
}

III 预备储备:Target-Action设计模式的运用

Target-Action指当某个事件发生的时候,调用特定对象的特定方法。https://blog.csdn.net/z929118967/article/details/108011011

3.1 响应者链

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件,我们称之为“响应者对象”。UIApplication、UIViewController、UIView都继承于UIResponder。

UIResponder内部提供了以下方法来处理事件

代码语言:javascript
复制
//一根或者多根手指开始触摸view,系统会自动调用view的下面方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//一根或者多根手指离开view,系统会自动调用view的下面方法
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法[可选]
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;


响应者链的事件传递过程

Responder Chain(响应者链)【上篇】:https://kunnan.blog.csdn.net/article/details/122809496

Responder Chain(响应者链)【下篇】:https://kunnan.blog.csdn.net/article/details/122811653

3.2 限制按钮的点击频率

在项目开发中,会对数据库数据进行更新操作的接口请求,不仅服务器侧需要控制请求频率以及保证数据的唯一性和一致性,app侧也需要进行限制来避免产生垃圾数据。

常用的方案有:

  1. 限制按钮的点击频率: 针对注册类接口的时间间隔timeInterval可设置长些,推荐0.5s
  2. 新增标志对单个接口进行请求频率的控制

3.2 右滑返回

原理:利用系统的返回手势interactivePopGestureRecognizer进行实现

使用场景:返回按钮有点小,不好触发返回时,可借助右滑返回来提升用户体验

https://blog.csdn.net/z929118967/article/details/118798618

3.3 应用程序初识

https://blog.csdn.net/z929118967/article/details/123184534每一个应用都有自己的UIApplication对象,而且是单例的;[UIApplication sharedApplication]

  1. 一个iOS程序启动后创建的第一个对象,就是UIApplication对象
  2. 利用UIApplication对象可以进行一些应用级的操作: 状态栏管理、事件处理
代码语言:javascript
复制
int main(int argc, char * argv[]) {
    @autoreleasepool {
        //principalClassName:        The name of the UIApplication class or subclass. If you specify nil, UIApplication is assumed.
//ERPApplication
        
        return UIApplicationMain(argc, argv, @"ERPApplication", NSStringFromClass([AppDelegate class]));
        
    }
}

  1. 在app受到干挠(来电、锁屏)时,会产生些系统事件,让delegate处理这些系统事件
  • 应用程序的生命周期(程序启动、关闭): 处理苹果服务器推送的消息。
  • 系统事件(来电)
  • 内存警告
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS逆向 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • I 网络加载中处理
    • 1.1 UIWindowLevel
      • 1.2 图层样式
        • 1.3 灵活控制是否允许交互
          • 1.4 点击空白处隐藏提示框
          • II 暂无数据处理
            • 2.1 用法
              • 2.2 核心实现
              • III 预备储备:Target-Action设计模式的运用
                • 3.1 响应者链
                  • 3.2 限制按钮的点击频率
                    • 3.2 右滑返回
                      • 3.3 应用程序初识
                      相关产品与服务
                      云服务器
                      云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档