美文网首页
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