无数据、无网络界面【更多的是开源思想】

一、前言

1、之前写了一篇 UIView 的分类,一句代码显示无数据界面 ,如果针对 tableView 或者 collectionView 使用起来还是挺麻烦的,简单分析一下吧

  • 优点: 适用范围比较广泛,只要界面是 UIView 或 其子类 ,都适用

  • 缺点:

    • 需要调用者手动管理(创建显示和隐藏),使用起来不方便。
    • 没有针对无网络进行封装,需要调用者在外界自己判断

2、先来看看本框架达到的效果吧,授人以鱼不如授人以渔!简单功能,本文做了详细分析,开源的更多是封装思想,所以文字比较多,请做好心理准备,但绝对有所收获的

无网络显示界面,动态适配
无数据显示界面,动态适配

二、分析思考

  • 1、实际项目中使用显示无数据或者无网络界面 ,一般都是 UITableView 或者 UICollectionView 此时如果使用 UIView 的分类 相对麻烦很多,如果是给旧项目添加这个功能,修改量就很大了,因为需要手动管理显示和隐藏

  • 2、那么如何避免手动管理呢?考虑到 UITableViewUICollectionView 两个都有 reloadData 方法,调用一次就会重新重新执行 dataSource 数据源方法,实际项目中,我们请求网络拿到列表数据后,都需要调用 reloadData ,而恰恰这个时候,为了更好的用户体验,我们也需要处理是否无数据或者无网络,如果没网络,需要显示无网络界面;而无数据就要显示无数据界面,那能不能在 reloadData 方法里面就处理了,或许你已经想到了

  • 3、对的,用runtime 替换掉tableView 或者 collectionView 的 reloadData 方法,然后在替换的方法里面处理好显示界面的逻辑,此时每当执行 reloadData 的时候,就自动判断需要显示什么界面,调用者不需要手动管理

  • 4、要替换系统的 reloadData 方法,有两种方式,分类和继承,原理都一样,本文就使用分类对UITableView 进行分析,当然UICollectionView 也是一样的,思路一样,如果需要,大家可自行实现

  • 5、需要什么样的功能

    • (1)参考不同的app,有些 app 显示无数据界面是一张gif 图,当然主流的都是 静态图 ,因此必须支持静图和动图的显示

    • (2)图片数据一般来自本地,但有可能来自网络(后台可以随时更换显示的无数据图,更新维护相对方便),因此必须要支持网络url下载,当然,为了更好的用户体验,网络图片下载后都需要缓存起来,下次就不需要再请求网络,而且,本地的gif也需要缓存到内存中,为了加快读取速度,可以参考SDWebImage,内存和沙盒都缓存起来,先从内存中获取,没有再从沙盒中获取,再没有才请求网络;既然有缓存,肯定也需要清空缓存

    • ** (3)考虑到此时可能会显示或者隐藏 UINavigationBarUITabBar ,那么这个无数据或无网络界面也需要动态更新布局,填充界面,不能留空白**

    • (4)当然还需要处理点击事件,考虑到分类拓展性不强,因此默认是整个界面点击,如果你是用继承实现,这就好办,还可以提供自定义界面(custom view)等等,本文就不作分析了

三、API 设计

1、是否开启缓存,默认开启,开启后,会缓存到沙盒 以及 内存,如果是本地gif图片,也会缓存到内存

/**
 *  @author gitKong
 *
 *  是否开启自动缓存,此时会缓存到沙盒 和 内存中,默认开启
 */
@property (nonatomic,assign)BOOL fl_autoCache;

2、没有数据显示的图片,不能为nil(内部有断言),可以传入本地图片名 或者 网络URL (包括gif,如果本地gif 图,需要加上后缀)

/**
 *  @author gitKong
 *
 *  没有数据显示的图片,不能为nil
 *
 *  可传入 本地图片名 或者 网络URL (包括gif)
 */
@property (nonatomic,copy)NSString *fl_noData_image;

3、没有网络显示的图片,不能为nil(内部有断言),可以传入本地图片名 或者 网络URL (包括gif,如果本地gif 图,需要加上后缀)

/**
 *  @author gitKong
 *
 *  没有网络显示的图片,不能为nil
 *
 *  可传入 本地图片名 或者 网络URL (包括gif)
 */
@property (nonatomic,copy)NSString *fl_noNetwork_image;

4、没有网络或者没有数据显示界面的点击事件,默认是整个界面的点击响应。如果自定义需求比较大,建议使用继承实现。

/**
 *  @author gitKong
 *
 *  没有网络或者没有数据显示界面的点击事件
 */
- (void)fl_imageViewClickOperation:(void(^)())clickOperation;

5、清空缓存,包括沙盒 和 内存中的都会清空,如果需要单独清空,可以从 实现文件 中开放出来

/**
 *  @author gitKong
 *
 *  清空缓存(包括沙盒和内存)
 */
- (void)fl_clearCache;

四、关键代码分析

1、Swizzling方法替换,在load 方法(load是只要类所在文件被引用就会被调用)中实现,如果方法存在那么直接替换方法,如果不存在则交换方法实现,替换tableView的 reloadData 方法,内部处理是否有网络或者有数据显示的界面

+ (void)fl_methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{
    Class class = [self class];
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    BOOL didAddMethod = class_addMethod(class,originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(class,swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    }
    else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

2、判断网络状态,考虑到如果使用 Reachability 需要导入文件,有一定的耦合性,不方便移植,因此本框架是通过获取状态栏的信息来判断,通过 runtime && KVC 就很容易获取状态栏的信息(runtime 可以知道 UIStatusBar 的所有属性信息,KVC 进行属性操作),经测试发现,飞行模式和关闭移动网络都拿不到 dataNetworkType 属性信息,1 - 2G; 2 - 3G; 3 - 4G; 5 - WIFI

- (BOOL)checkNoNetwork{
    BOOL flag = NO;
    UIApplication *app = [UIApplication sharedApplication];
    NSArray *children = [[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
    int netType = 0;
    //获取到网络返回码
    for (id child in children) {
        if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
            //获取到状态栏,飞行模式和关闭移动网络都拿不到dataNetworkType;1 - 2G; 2 - 3G; 3 - 4G; 5 - WIFI
            netType = [[child valueForKeyPath:@"dataNetworkType"] intValue];
            
            switch (netType) {
                case 0:
                    flag = NO;
                    //无网模式
                    break;
                    
                default:
                    flag = YES;
                    break;
            }
        }
    }
    return flag;
}

3、判断是否有数据 直接通过 dataSource 获取对应的 sectionrow 进行判断,只要 row 不为空,那么就证明有数据

- (BOOL)checkNoData{
    NSInteger sections = 1;
    NSInteger row = 0;
    BOOL isEmpty = YES;
    if ([self.dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
        sections = [self.dataSource numberOfSectionsInTableView:self];
    }
    for (NSInteger section = 0; section < sections; section++) {
        if ([self.dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
            row = [self.dataSource tableView:self numberOfRowsInSection:section];
            if (row) {
                // 只要有值都不是空
                isEmpty = NO;
            }
            else{
                isEmpty = YES;
            }
        }
    }
    return isEmpty;
}

4、判断NavigationBar和TabBar 显示隐藏,更新界面的布局,填充不留空白

  • 判断NavigationBar (提供三种方案)
  • 通过 runtime 发现 UITableView 有 一个隐藏属性 visibleBounds ,直译过来就是可视区域,通过实测,如果没有导航控制器,那么 visibleBoundsy = 0,如果有导航控制器,而且UINavigationBar 是显示,那么y = -64,如果有导航控制器,但UINavigationBar隐藏,那么y = -20可以通过这个来判断导航栏是否隐藏;
  • 当然这个确实麻烦点,可以使用 我之前的文章 任意NSObject及其子类中获取当前显示的控制器 此时可以获取当前显示的控制器,然后判断NavigationBar 显示隐藏
  • 当然,还有一种办法,不需要去手动判断, UITableView 有 还有一个隐藏属性 wrapperView 这个 view 可以在 debug view Hieratrchy 里面看到层级结构,通过实测,这个会随着导航栏显示隐藏 来改变 y 的偏移,因此直接将无数据或者无网络页面添加到 wrapperView 上就可以了
  • 判断TabBar:本来打算通过 [UITabBar appearance] 来获取,发现虽然不会报错,但测试发现没任何效果,通过断点po提示 <_UIAppearance:0x17025b000> <Customizable class: UITabBar> with invocations (null)> 是空的,不能获取到,当然 [UINavigationBar appearance] 也没效果,所以此时使用 任意NSObject及其子类中获取当前显示的控制器 来判断TabBar是否显示
- (void)updataImageViewFrame{
    // 如果没有导航控制器,那么rect的y值为0,如果有导航控制器,那么y为-64,如果导航控制器hidden那么也会跟着变,不需要额外修改
    Class conecreteValue = NSClassFromString(@"NSConcreteValue");
    id concreteV = [[conecreteValue alloc] init];
    concreteV = [self valueForKey:@"visibleBounds"];
    CGRect rect ;
    [concreteV getValue:&rect];
    
    // 判断是否有tabBar显示
    // 注意:分类中使用[UITabBar appearance] 和 [UINavigationBar appearance] 都不能获取对象,断点po提示<_UIAppearance:0x17025b000> <Customizable class: UITabBar> with invocations (null)>
    UIViewController *currentVc = [self fl_viewController];
    UITabBarController *tabVc = (UITabBarController *)currentVc.tabBarController;
    if (tabVc) {
        self.imageView.frame = CGRectMake(rect.origin.x, 0, rect.size.width, rect.size.height + rect.origin.y - (tabVc.tabBar.hidden ? 0 : tabVc.tabBar.bounds.size.height));
    }
    else{
        self.imageView.frame = CGRectMake(rect.origin.x, 0, rect.size.width, rect.size.height + rect.origin.y);
    }
}

5、获取GIF 图片 每一帧播放时长,通过一个key kCGImagePropertyGIFUnclampedDelayTime 可以获取,然后拼接起来,播放GIF 图片

- (CGFloat)durationWithSource:(CGImageSourceRef)source atIndex:(NSUInteger)index {
    float duration = 0.1f;
    CFDictionaryRef propertiesRef = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
    NSDictionary *properties = (__bridge NSDictionary *)propertiesRef;
    NSDictionary *gifProperties = properties[(NSString *)kCGImagePropertyGIFDictionary];
    
    NSNumber *delayTime = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
    if (delayTime) duration = delayTime.floatValue;
    else {
        delayTime = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
        if (delayTime) duration = delayTime.floatValue;
    }
    CFRelease(propertiesRef);
    return duration;
}

五、总结

  • 1、加载GIF 图片内存占用挺大,特别是缓存到内存中,内存会飙升,注意使用,测试发现SDWebImage 也会出现内存飙升,YYImageCache 的话就优化很多,待优化

  • 2、分类中使用 [UITabBar appearance][UINavigationBar appearance] 都不能获取对象,断点po提示<_UIAppearance:0x17025b000> <Customizable class: UITabBar> with invocations (null)>

  • 3、因为判断网络是通过获取状态栏信息来判断,如果 是 CMCC 连接的WI-FI,就不能正确判断网络是否已联网

  • 4、此框架零耦合,方便移植,使用方便,只需要设置 fl_noData_imagefl_noNetwork_image ,只要调用 reloadData 就会自动判断需要显示什么界面

  • 4、上文中提到的功能点都实现了,简单的功能,但做了详细的分析,从需求确定-功能分析-技术实现都做了详细的分析,封装的思想才是关键,开源不单单是代码,更多的是封装的思想

  • 5、具体实现代码比较多,本文就不一一详细讲解,Github Demo 中有 对应的注释,欢迎大家关注我,喜欢给个like 和 star,会随时开源~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,756评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,059评论 4 62
  • 一.今天课堂老师主要讲了 switch 多分支语句。 结构: switch(条件表达式) { case 常量表达式...
    李响lx阅读 208评论 0 0
  • [连载]我们离婚了--目录页 我顺从地跟着林清出了大楼,然后坐了一辆出租车赶往机场。出租车司机是一位40多岁的中年...
    廉子阅读 342评论 0 3
  • 回到北京一个月了,最近晚上加班很多,回家的时候已经接近11点,回去的路上有一段没有路灯,路两侧的槐树已经有十年树龄...
    京城逍遥客阅读 169评论 0 3