美文网首页
DZNEmptyDataSet

DZNEmptyDataSet

作者: 我有小尾巴快看 | 来源:发表于2017-11-24 17:06 被阅读35次

    在IOS开发中,UITableView是使用较多的一个控件。如果UITableView在数据源没有数据时会是一片空白,用户体验较差。所以在数据源为空的时候,我们应该给用户一些相应的提示。而DZNEmptyDataSet就是用来解决这个问题的,简单快捷的帮我们解决了空数据时的界面处理。
    使用CocoaPods可以导入,pod 'DZNEmptyDataSet' Github地址

    简单使用

    - (UITableView *)tableView {
        if (!_tableView) {
            _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
            _tableView.delegate = self;
            _tableView.dataSource = self;
            _tableView.emptyDataSetSource = self;
            _tableView.emptyDataSetDelegate = self;
            _tableView.tableFooterView = [[UIView alloc]initWithFrame:CGRectMake(0,0,0,CGFLOAT_MIN)];
        }
        return _tableView;
    }
    
    - (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state {
        return [UIImage imageNamed:@"image"];
    }
    
    - (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button {
        NSLog(@"%s",__func__);
    }
    

    只需设置代理并且实现对应的方法就可以完成string,image等效果,并且可以添加点击事件。

    DZNEmptyDataSet文件

    DZNEmptyDataSet很简单,只有一个文件。两个代理,分别是DZNEmptyDataSetDelegate和DZNEmptyDataSetSource。

    • DZNEmptyDataSetDelegate
    //是否淡入显示,默认YES
    - (BOOL)emptyDataSetShouldFadeIn:(UIScrollView *)scrollView;
    
    //当数据源为空时仍然展示,默认为NO不显示
    - (BOOL)emptyDataSetShouldBeForcedToDisplay:(UIScrollView *)scrollView;
    
    //是否能显示。默认YES显示
    - (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView;
    
    //是否支持点击,默认YES可点击
    - (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView;
    
    //是否允许滚动,默认NO不可滚动
    - (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView;
    
    //是否播放imageView(图片数组播放),默认NO不播放
    - (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView;
    
    //view的点击事件
    - (void)emptyDataSet:(UIScrollView *)scrollView didTapView:(UIView *)view;
    
    //button的点击事件
    - (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button;
    
    //emptyDataSet将要出现
    - (void)emptyDataSetWillAppear:(UIScrollView *)scrollView;
    
    //emptyDataSet已经出现
    - (void)emptyDataSetDidAppear:(UIScrollView *)scrollView;
    
    //emptyDataSet将要消失
    - (void)emptyDataSetWillDisappear:(UIScrollView *)scrollView;
    
    //emptyDataSet已经消失
    - (void)emptyDataSetDidDisappear:(UIScrollView *)scrollView;
    
    • DZNEmptyDataSetSource
    //标题
    - (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView;
    
    //描述
    - (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView;
    
    //图片
    - (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView;
    
    //设置图片渲染色
    - (UIColor *)imageTintColorForEmptyDataSet:(UIScrollView *)scrollView;
    
    //图片动画
    - (CAAnimation *) imageAnimationForEmptyDataSet:(UIScrollView *) scrollView;
    
    //设置button不同state下的标题
    - (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
    
    //设置button不同state下的图片
    - (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
    
    //设置button背景图片,没有默认值
    - (UIImage *)buttonBackgroundImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
    
    //设置背景颜色
    - (UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView;
    
    //自定义想要显示的view(而不是自带的label,imageview,button)
    - (UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView;
    
    //垂直偏移量
    - (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView;
    
    //两个对象的垂直偏移量,默认11
    - (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView;
    

    源码分析

    setEmptyDataSetSourcesetEmptyDataSetDelegate中,如果代理为空或者不允许显示时调用dzn_invalidate释放self. emptyDataSetView
    否则当代理存在并且允许显示时,动态添加属性emptyDataSetSource或者emptyDataSetDelegate。并且将dzn_reloadData注入到reloadData方法中,涂过是UITableview,同时注入到endUpdates中。

    - (void)setEmptyDataSetSource:(id<DZNEmptyDataSetSource>)datasource {
        if (!datasource || ![self dzn_canDisplay]) [self dzn_invalidate];
        
        objc_setAssociatedObject(self, kEmptyDataSetSource, [[DZNWeakObjectContainer alloc] initWithWeakObject:datasource], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
        // 将dzn_reloadData注入到系统的reloadData中
        [self swizzleIfPossible:@selector(reloadData)];
        
        // 如果是UITableView,则额外将dzn_reloadData注入到系统endUpdates中
        if ([self isKindOfClass:[UITableView class]]) {
            [self swizzleIfPossible:@selector(endUpdates)];
        }
    }
    
    - (void)setEmptyDataSetDelegate:(id<DZNEmptyDataSetDelegate>)delegate {
        if (!delegate) [self dzn_invalidate];
        objc_setAssociatedObject(self, kEmptyDataSetDelegate, [[DZNWeakObjectContainer alloc] initWithWeakObject:delegate], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    dzn_invalidate先发送视图将要消失的通知,如果self.emptyDataSetView存在则释放其子视图及约束,从父视图中移除,最后将其置空。恢复ScrollView的可滚动性,最后发送视图已消失的通知。

    - (void)dzn_invalidate {
        // Notifies that the empty dataset view will disappear
        [self dzn_willDisappear];
        
        if (self.emptyDataSetView) {
            [self.emptyDataSetView prepareForReuse];
            [self.emptyDataSetView removeFromSuperview];
            
            [self setEmptyDataSetView:nil];
        }
        
        self.scrollEnabled = YES;
        [self dzn_didDisappear];
    }
    
    - (void)prepareForReuse {
        [self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
        
        _titleLabel = nil;
        _detailLabel = nil;
        _imageView = nil;
        _button = nil;
        _customView = nil;
        
        [self removeAllConstraints];
    }
    

    swizzleIfPossible用于给系统方法添加一些内容。

    - (void)swizzleIfPossible:(SEL)selector {
        // 检查传入的方法是否实现,未实现则return
        if (![self respondsToSelector:selector]) {
            return;
        }
        
        //创建表保存已注入的方法(给传入的方法注入dzn_reloadEmptyDataSet,再调用该方法。)
        //即调用reloadData前或tableview调用endUpdate前先调用dzn_reloadEmptyDataSet。
        if (!_impLookupTable) {
            // 3 种支持的类型,UIS从rollView,UITableView,UIColloectionView
            _impLookupTable = [[NSMutableDictionary alloc] initWithCapacity:3];
        }
        
        // We make sure that setImplementation is called once per class kind, UITableView or UICollectionView.
        for (NSDictionary *info in [_impLookupTable allValues]) {
            Class class = [info objectForKey:DZNSwizzleInfoOwnerKey];
            NSString *selectorName = [info objectForKey:DZNSwizzleInfoSelectorKey];
            
            if ([selectorName isEqualToString:NSStringFromSelector(selector)]) {
                if ([self isKindOfClass:class]) {
                    return;
                }
            }
        }
        
        //获取self的基类(scroll,table,collection)
        Class baseClass = dzn_baseClassToSwizzleForTarget(self);
        //获取方法名(dzn_implementationKey返回 className_SELName 的拼写)
        NSString *key = dzn_implementationKey(baseClass, selector);
        //获取以方法名对应的方法体
        NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey];
        
        // 如果方法体存在或者key不存在或者基类不是规定的那三种则跳过
        if (impValue || !key || !baseClass) {
            return;
        }
        
        //在传入方法中注入dzn_reloadEmptyDataSet,然后调用传入的方法。
        Method method = class_getInstanceMethod(baseClass, selector);
        IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
        
        //保存信息
        NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass,
                                       DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
                                       DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
        
        [_impLookupTable setObject:swizzledInfo forKey:key];
    }
    
    

    reloadEmptyDataSet调用了私有方法dzn_reloadEmptyDataSet

    - (void)reloadEmptyDataSet {
        [self dzn_reloadEmptyDataSet];
    }
    
    - (void)dzn_reloadEmptyDataSet {
        if (![self dzn_canDisplay]) return;//如果数据源存在并且实现了代理则显示,否则返回
        //如果可以允许显示并且dataSouce的item count为0可以显示,如果无论是否有数据都一直显示的代理被返回YES也能显示。
        if (([self dzn_shouldDisplay] && [self dzn_itemsCount] == 0) || [self dzn_shouldBeForcedToDisplay]) {
            [self dzn_willAppear];
            //获取当前的emptyDataSetView
            DZNEmptyDataSetView *view = self.emptyDataSetView;
            //如果它没有被加入则加入并且要放在最前方。
            if (!view.superview) {
                if (([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]]) && self.subviews.count > 1) {
                    [self insertSubview:view atIndex:0];
                }
                else {
                    [self addSubview:view];
                }
            }
            
            //此时移除子视图(为了等会重新添加子视图刷新内容做准备)
            [view prepareForReuse];
            //如果实现了自定义的视图则赋值给view.customView,否则重新给view.customView添加默认控件。
            UIView *customView = [self dzn_customView];
            
            if (customView) {
                view.customView = customView;
            }
            else {
                NSAttributedString *titleLabelString = [self dzn_titleLabelString];
                NSAttributedString *detailLabelString = [self dzn_detailLabelString];
                
                UIImage *buttonImage = [self dzn_buttonImageForState:UIControlStateNormal];
                NSAttributedString *buttonTitle = [self dzn_buttonTitleForState:UIControlStateNormal];
                
                UIImage *image = [self dzn_image];
                UIColor *imageTintColor = [self dzn_imageTintColor];
                UIImageRenderingMode renderingMode = imageTintColor ? UIImageRenderingModeAlwaysTemplate : UIImageRenderingModeAlwaysOriginal;
                
                view.verticalSpace = [self dzn_verticalSpace];
                
                if (image) {
                    if ([image respondsToSelector:@selector(imageWithRenderingMode:)]) {
                        view.imageView.image = [image imageWithRenderingMode:renderingMode];
                        view.imageView.tintColor = imageTintColor;
                    }
                    else {
                        view.imageView.image = image;
                    }
                }
                
                if (titleLabelString) {
                    view.titleLabel.attributedText = titleLabelString;
                }
                
                if (detailLabelString) {
                    view.detailLabel.attributedText = detailLabelString;
                }
                
                if (buttonImage) {
                    [view.button setImage:buttonImage forState:UIControlStateNormal];
                    [view.button setImage:[self dzn_buttonImageForState:UIControlStateHighlighted] forState:UIControlStateHighlighted];
                }
                else if (buttonTitle) {
                    [view.button setAttributedTitle:buttonTitle forState:UIControlStateNormal];
                    [view.button setAttributedTitle:[self dzn_buttonTitleForState:UIControlStateHighlighted] forState:UIControlStateHighlighted];
                    [view.button setBackgroundImage:[self dzn_buttonBackgroundImageForState:UIControlStateNormal] forState:UIControlStateNormal];
                    [view.button setBackgroundImage:[self dzn_buttonBackgroundImageForState:UIControlStateHighlighted] forState:UIControlStateHighlighted];
                }
            }
            
            view.verticalOffset = [self dzn_verticalOffset];
            
            view.backgroundColor = [self dzn_dataSetBackgroundColor];
            view.hidden = NO;
            view.clipsToBounds = YES;
            
            view.userInteractionEnabled = [self dzn_isTouchAllowed];
            
            view.fadeInOnDisplay = [self dzn_shouldFadeIn];
            
            [view setupConstraints];
            
            [UIView performWithoutAnimation:^{
                [view layoutIfNeeded];            
            }];
            
            self.scrollEnabled = [self dzn_isScrollAllowed];
            
            if ([self dzn_isImageViewAnimateAllowed])
            {
                CAAnimation *animation = [self dzn_imageAnimation];
                
                if (animation) {
                    [self.emptyDataSetView.imageView.layer addAnimation:animation forKey:kEmptyImageViewAnimationKey];
                }
            }
            else if ([self.emptyDataSetView.imageView.layer animationForKey:kEmptyImageViewAnimationKey]) {
                [self.emptyDataSetView.imageView.layer removeAnimationForKey:kEmptyImageViewAnimationKey];
            }
            
            [self dzn_didAppear];
        }
        else if (self.isEmptyDataSetVisible) {
            [self dzn_invalidate];
        }
    }
    

    相关文章

      网友评论

          本文标题:DZNEmptyDataSet

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