在日常开发中经常涉及数据列表的查询,处理服务侧无数据返回的情况或者网络异常的手段是iOS必备小技能。
如果是iOS新手,可以先看第三章节的预备知识。
本文以SVProgressHUD
框架为例子进行讲解
UIWindowLevel
级别的高低顺序从小到大为Normal < StatusBar < Alert,Level高的将排在所有Level比他低的层级的前面。
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar API_UNAVAILABLE(tvos);
SVProgressHUD 相关方法
[SVProgressHUD setMaxSupportedWindowLevel:UIWindowLevelStatusBar];
默认图层样式显示的时候,允许用户交互适合大部分场景。但是像注册、登录、切换账号的场景建议使用不允许交互样式。
请求包含这些关键字的API时,建议不允许交互 :
Create
、Add
、Update
、Delete
、Bind
、Submit
、Save
、Login
、Change
、Set
、Edit
、Modify
、Cancel
、Send
、Check
、Audit
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
};
setInfoImage:
// 设置加载信息
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:@""];
showWithStatus
[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:@"加载中..."];
思路:通过请求方法入口进行控制是否允许交互 实现步骤:
showLoadingDataGif
时判断//
// 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
实现思路: 自定义应用对象UIApplication来全局监听用户点击事件
实现步骤:
sendEvent:
@implementation ERPApplication
- (void)sendEvent:(UIEvent *)event
{
//如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者。
[super sendEvent:event];//这里一定不能漏掉
///点击空白处隐藏提示框:
[ERPLoading setupTouch4hiddenLoadingDataGif:event];
}
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.
SVProgressHUDMaskTypeNone
则调用dismiss。+ (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)];
}
网络请求失败,业务逻辑错误,返回数据为空都是需要处理界面的显示,推荐使用暂无数据进行提示。
在这里插入图片描述
if (weakSelf.viewModel.listDataArray.count == 0) {
[weakSelf.viewModel.ShowNoviewSubject sendNext:QCTLocal(CRM_nodata_Info)];
}else{
[weakSelf.viewModel.hidenNoviewSubject sendNext:nil];
}
V层初始化暂无数据视图:将视图添加到tableView,这样可以不影响下拉刷新和上拉加载
- (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层的事件
[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"];
}];
暂无数据视图的实现
// 显示暂无数据图片
- (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;
}
Target-Action指当某个事件发生的时候,调用特定对象的特定方法。https://blog.csdn.net/z929118967/article/details/108011011
在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件,我们称之为“响应者对象”。UIApplication、UIViewController、UIView都继承于UIResponder。
UIResponder内部提供了以下方法来处理事件
//一根或者多根手指开始触摸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
在项目开发中,会对数据库数据进行更新操作的接口请求,不仅服务器侧需要控制请求频率以及保证数据的唯一性和一致性,app侧也需要进行限制来避免产生垃圾数据。
常用的方案有:
原理:利用系统的返回手势interactivePopGestureRecognizer进行实现
使用场景:返回按钮有点小,不好触发返回时,可借助右滑返回来提升用户体验
https://blog.csdn.net/z929118967/article/details/118798618
https://blog.csdn.net/z929118967/article/details/123184534每一个应用都有自己的UIApplication对象,而且是单例的;[UIApplication sharedApplication]
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]));
}
}