iOSUI进价程序员

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

2016-12-07  本文已影响2153人  gitKong

一、前言

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

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

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

二、分析思考

三、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 显示隐藏,更新界面的布局,填充不留空白

- (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;
}

五、总结

上一篇下一篇

猜你喜欢

热点阅读