美文网首页iOS进阶+实战iOS菜鸟到大神iOS 准备学习
零行代码为App添加异常加载占位图

零行代码为App添加异常加载占位图

作者: 卖报的小画家Sure | 来源:发表于2016-12-01 17:47 被阅读5936次
前文提要

近期准备重构项目,需要重写一些通用模块,正巧需要设置App异常加载占位图的问题,心血来潮设想是否可以零行代码解决此问题,特在此分享实现思路。

思路分享

对于App占位图,通常需要考虑的控件有tableView、collectionView和webView,异常加载情况区分为无数据和网络异常等。

既然要实现零代码形式,因此就不能继承原始类重写或添加方法等方式,而是通过对对应控件添加类别(分类)来实现。

简单来说,以tableView为例实现思路为每当tableView调用reloadData进行刷新时,检测此时tableView行数,若行数不为零,正常显示数据。若行数为零,说明无数据显示占位图。

添加占位图的方式有很多种,例如借助tableView的backgroundView或直接以addSubView的方式添加,这里采用的为addSubView方式,尽量避免原生属性的占用。

对于检测tableView数据是否为空,借助tableView的代理dataSource即可。核心代码如下,依次获取tableView所具有的组数与行数,通过isEmpty这个flag标示最后确定是否添加占位图。

- (void)checkEmpty {
    BOOL isEmpty = YES;//flag标示
    
    id <UITableViewDataSource> dataSource = self.dataSource;
    NSInteger sections = 1;//默认一组
    if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
        sections = [dataSource numberOfSectionsInTableView:self] - 1;//获取当前TableView组数
    }
    
    for (NSInteger i = 0; i <= sections; i++) {
        NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i];//获取当前TableView各组行数
        if (rows) {
            isEmpty = NO;//若行数存在,不为空
        }
    }
    if (isEmpty) {//若为空,加载占位图
        if (!self.placeholderView) {//若未自定义,展示默认占位图
            [self makeDefaultPlaceholderView];
        }
        self.placeholderView.hidden = NO;
        [self addSubview:self.placeholderView];
    } else {//不为空,隐藏占位图
        self.placeholderView.hidden = YES;
    }
}

相应的对于CollectionView亦可通过numberOfSectionsInCollectionView:collectionView:numberOfItemsInSection获取其组数和行数,这里就不一一赘述。

需要注意的为webView占位图是否显示的判断,一种情况为webView调用其webView: didFailLoadWithError:方法,第二种为webView完成加载显示为空的情况。但存在的一个问题是,webView没有必选的协议方法,或可能根本没有设置代理。因此无法很好的判断webView是否响应其协议方法。因此该demo暂时没有添加webView的占位图,如果有好的想法可以评论指出。

接下来说最重要的一步,如何实现零行代码添加占位图呢?

其实实现思路非常简单,如果可以让tableView在执行reloadData时自动检测其行数就可以了。也就是我们需要在reloadData原有方法的基础上添加checkEmpty此方法。

这里又能体现到Runtime Method Swizzling的作用了,我们可以通过Method Swizzling替换reloadData方法,给予它新的实现。核心代码如下:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //方法交换,将reloadData实现交换为sure_reloadData
        [self methodSwizzlingWithOriginalSelector:@selector(reloadData) bySwizzledSelector:@selector(sure_reloadData)];
    });
}

- (void)sure_reloadData {
    [self checkEmpty];
    [self sure_reloadData];
}

这样就可以实现reloadData的同时检测行数从而判断是否显示占位图的功能。
这里采用了上篇文章《Runtime Method Swizzling开发实例汇总》的代码用例类NSObject+Swizzling.h,因此该篇文章也算上篇文章的延续,为Runtime Method Swizzling的另一种用例。感兴趣的朋友可以前往阅读更多的实用用例。

为实现零代码的效果,代码中已添加了placeholder视图的默认样式,如图所示:


占位图样式

若要实现效果图中点击图标重新刷新效果,需要让tableView调用reloadBlock,因为数据的刷新大多是不同的,所以具体刷新执行代码还是需要自己手动设置的。若不需要,则无需添加此操作。

[_tableView setReloadBlock:^{
      //刷新操作
}];

如果需要自定制占位视图样式也非常简单,因占位图样式比较统一,所以可直接修改SurePlaceholderView占位图类以达到自己想要的效果,再而在UITableView+Sure_Placeholder.hUICollectionView+Sure_Placeholder.hUIWebView+Sure_Placeholder.h类别中均外漏了placeholderView属性,将其赋值为新的视图亦可。

以tableView为例,可以通过如下方式进行修改

_tableView.placeholderView =[[CustomPlaceholderView alloc]initWithFrame:_tableView.bounds];

同样的对于无数据与无网络的效果切换,也可以通过网络是否可用的标示来进行展示不同的占位图。例如

if (is_Net_Available) {
   _tableView.placeholderView = [[CustomPlaceholderView alloc]initWithFrame:_tableView.bounds];
 } else {
   _tableView.placeholderView = [[NetNoAvailableView alloc]initWithFrame:_tableView.bounds];
}

为方便大家阅读和修改,demo已上传github。

下载链接如下:
零行代码为App添加无数据占位图🔗

既然为零代码,因此使用方法将Sure_Placeholder文件夹拖入工程即可。有任何问题大家可以评论指出。

目前已修复问题与功能添加
  • 修复固定数据多组显示问题
  • 修复首次网络请求未完成占位图显示问题
    因tableView、collectionView在创建后系统会默认调用一次reloadData,所以会出现网络请求未完成即展示占位图的问题。类别中新增加属性firstReload(首次加载)加以限制,若希望在首次网络请求未完成时不显示占位图,可将firstReload置为YES即可。
_tableView.firstReload = YES;

代码持续更新中,欢迎大家 check out!

相关文章

网友评论

  • 不喜欢说话的小张同学:大佬我来水一波。
    关于获取数据源这个 苹果提供一个visableCells数组,应该可以通过这个直接取cell数量, 避免多层判断代理方法了,。
    另外第一次刷新,似乎不是所有的版本/机型默认第一次刷新的样子
    https://github.com/zhangxuchuan827/XCNoDataPlaceholder
  • 木头Lee:方法内部调用自身,会造成死循环么?

    - (void)sure_reloadData {
    [self checkEmpty];
    [self sure_reloadData];
    }
    inxx:@木头Lee 不会,方法交换Method Swizzling在自定义的交换体内调用自身其实是去调用原来类的相应方法...给我自己都说绕了..
    木头Lee:另外,利用runtime替换系统方法,会影响上架吗?
  • Frey丶:我硕哥的代码还是简洁易懂.粗暴靠谱. 赞👍
  • 雪_晟:第一次走reload data 是因为设置了代理,可以初始化不设置代理,在请求数据完成的时候设置代理
  • 奈焚摩尔:我一般在viewModel中你请求完数据接口,并解析完成,然后block回调设置两个出来 一个是请求到数据的 一个是没有数据的。不过你这个reload的想法真的是好有意思啊 我也去折腾下玩玩 贼棒
  • liangdahong:博主好,
    UITableView+Sure_Placeholder.m 文件的 22-28行
    标注是否为第一次加载是否有问题啊?

    - (void)sure_reloadData {
    if (是否加载过) {
    [self checkEmpty];
    }
    是否加载过 = YES;
    [self sure_reloadData];
    }
    liangdahong:@卖报的小画家Sure 首次不是不想tableView加载展位图嘛? 想加载白板。
    卖报的小画家Sure:@idhong firstReload置为YES首次就不会走checkEmpty了
    liangdahong:是否应该怎样的代码,伪代码如上
  • 47200923d724:楼主,想办法弄个手动控制要不要占位图的功能呗!
    卖报的小画家Sure:@lin111111111 嗯呢,那也挺好的
    47200923d724:@卖报的小画家Sure
    自己加了个属性,默认是不使用的,需要使用再设置成YES,感觉这样人性化一点,免得跟前面的代码有冲突,手上这个项目好几年的了:grin:
    卖报的小画家Sure:目前如果不需要占位图可以这么设置_tableView.placeholderView = [[UIView alloc]init];
  • 2922fb61a40e:如果对一个方法hook多次会有问题吗?
  • 屈涯:我这里有个问题,希望能得到帮助。 我有两个section一个返回2个row,一个根据数组个数返回。 这时候如果数组没有数据。 第一个section内容也会不现实。
    _水畔竹汐:在项目某几个页面并不需要设置占位图怎么取消
    屈涯:@卖报的小画家Sure 返回section这个方法固定返回了2组。 -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

    return 2;
    }

    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    if (section == 0) {

    return 2;
    }
    return self.dataArray.count;
    } 是这样的。 现在是通过 self.tableView.firstReload = YES; 这样解决的。
    卖报的小画家Sure:返回section个数的代理方法是否固定返回2组呢?刚代码尝试了下你的说法并没问题。
  • DDDDeveloper:遇到一个问题,如果我的 row 全部为零,但是 section 有值,也会显示占位,怎么解决?比如实现一个可以折叠的类似 QQ 的 tableview,当 折叠起来的时候每个分组的 row 都是0.
    卖报的小画家Sure:你这种情况属于一种特例了,因为当前类就是遍历所有的section都没有row才确定数据为空从而加载占位图。推荐你在折叠tableView的类里自己添加判断决定是否使用占位图。
  • 水寒不知:博主。我想问下。为什么我的文件没有引用你的tableview的category。没数据的时候还是会出现占位符
    卖报的小画家Sure:@水寒不知 哦哦,我还以为你文件都没导入呢,既然category文件在,那功能一定是存在的,因为category为类别,就是为原有类(包括系统类)添加一些额外的方法。这个category每次都会在reloadData方法检测当前tableView行数,所以肯定会执行的。
    水寒不知:@卖报的小画家Sure 这个没有。百度上说是因为category不导入头文件还是会运行
    卖报的小画家Sure:怎么可能,应该是你之前书写过,或者在基类里设置过占位图
  • icc_tips:楼主,你这个键盘使用会出现问题吗??
    icc_tips:是的额
    ivylee_mr:你用了出问题了吗
  • DDDDeveloper:能否自己调整占位视图的位置?有的时候需要适配
  • DDDDeveloper:你好,我又遇到了一个问题。就是在打开相册时系统也是以 UITableView 展示的,我发现即使相册有内容也会显示出占位图。能不能搞个半自动的设置,就是我们在某些页面出现 bug 的时候可以手动控制不使用该框架。还有在键盘弹出来的时候,也会出现一部分占位图。
    卖报的小画家Sure:@一个有前途的男人 近期更新解决过这个问题,你更新下文件尝试下
  • 疾风小超:楼主,加入Sure_Placeholder,选择相册界面也会出现占位符,明明相册是存在图片的
    卖报的小画家Sure:@疾风小超 好,有问题的话可以再找我
    疾风小超:@卖报的小画家Sure 没有,前一段时间看到这篇技术文章的,我现在去更新一下文件
    卖报的小画家Sure:@疾风小超 确认使用的是最新的文件吗?近期修复过这个问题
  • fleeming:诚求楼主看到后与我交流答复我。我现在的需求是这样的,,我首页的tableHeaderView里放了好多其他元素。。用楼主的牛逼库之后,若没数据tableHeaderView也被遮盖了。求指教怎么限制某一个控制器的table不用你的占位图。。因为我首页列表即使一行数据也没有,但我tableHeaderView上的好多其他东西也应该要显示的,而不是一个大大占位图铺满全屏。
    fleeming:我明白了,,直接人工在不想要受到干扰的列表控制器里。判断即使numberOfRowsInSection返回0行时也手动隐藏placeholder。感谢。。
    卖报的小画家Sure:@Mingoy 如果不想更改库文件的话,可以在控制器中手动判断段数,若段数不为零,添加tableview.placeholder view.hidden=YES
    卖报的小画家Sure:@Mingoy 这种情况的话需要判断段数是否为零了,也就是需要在判断占位图显示的位置添加当前段数判断
  • 237369252642:获取 section 和 row 的数量有更好的方法
    ```
    open var numberOfSections: Int { get }
    open func numberOfRows(inSection section: Int) -> Int
    ```
    237369252642:@卖报的小画家Sure 你的 checkEmpty 方法里面用的是
    -[UITableViewDataSource numberOfSectionsInTableView:] 和
    -[UITableViewDataSource tableView:numberOfRowsInSection:]

    我说的是
    -[UITableView numberOfSections] 和
    -[UITableView numberOfRowsInSection:]
    卖报的小画家Sure:@wanganjun 文中用的就是这两个方法啊。。。
    237369252642:@wanganjun 忘了说, 上面的是 UITableView 的方法
  • R0b1n_L33:思路很好
    基本和DZNEmptyDataSet是一致的
    DZNEmptyDataSet 针对tableView还特别swizzle了endUpdates方法
    事实上,这些做法尚不完全
    对于tableView的- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
    以及collectionView的- (void)reloadSections:(NSIndexSet *)sections;
    是不会调用reloadData的
    这些都是考虑的方向
    卖报的小画家Sure:@ljysdfz 多谢,代码会持续更新实现新的需求 :smile:
  • DDDDeveloper:你好,有没有发现一个问题,如果我设置我的 section 为固定的3,会出现增加一个 section 的情况。我用你提供的 demo 演示过,我自己的项目也演示过。请问如何解决这个 bug?
    卖报的小画家Sure:@一个有前途的男人 能再详细说下你的代码是怎么写的吗?除了手动设置setion个数,还做了什么操作?
    DDDDeveloper:@卖报的小画家Sure 是的,我的数据源中 section 的个数是写死的。但是我发现会报错,仔细研究了一番发现数组越界,多了一个 section
    卖报的小画家Sure:增加一个section?变成4组?
  • 壹点微尘:当服务器的数据还未获取到时,页面会显示占位视图(个人觉得显示白板好些),数据获取到占位图才消失。。。。。
    卖报的小画家Sure:@Dave_Zone 嗯,注意到这个问题了,如果想要实现此需求,需要在tableView加载前将占位图隐藏,加载完成或失败后将其显示。或者使用加载栏进行遮挡。后续有更好的优化方案会给予更新。
    Sunday_David:@卖报的小画家Sure 确实是这样的bug,不是你所说的relodata才会调用,是+(void)load的时候就调用了
    卖报的小画家Sure:@壹点微尘 占位图只有tableView触发reloadData时才会展示或隐藏,在数据还未获取到时,为什么要先reladData一次呢?因此第一次加载为空白状态,数据加载完成若出现异常才会显示占位图。demo中为了演示无数据与有数据切换所以才手动在无数据时刷新,不要误解。

本文标题:零行代码为App添加异常加载占位图

本文链接:https://www.haomeiwen.com/subject/nsstmttx.html