iOS开发造* | UIView及其子类的占位图

点击上方“iOS开发”,选择“置顶公众号”

关键时刻,第一时间送达!

iOS开发造* | UIView及其子类的占位图

iOS开发造* | UIView及其子类的占位图


这是要封装的


iOS开发造* | UIView及其子类的占位图


这里展示的仅仅是tableView的占位图。


封装原因


最近重构分类详情页的时候需要实现这个功能,关于这个view我已经封装过几次了,今天我试着回想当初的思路,竟然没什么印象了!对于自己封装过的东西没什么印象原因只有一种:不够简洁。


iOS开发造* | UIView及其子类的占位图

此事无关代码,关乎优雅


然后我翻看了一下自己以前的写的关于封装这个view的简书,不得不说,挺搓的。。。(每次回头看曾经的代码,都不甚满意。。。)


曾经的问题


1.首先,在基类里实现这个功能就是变相的提升程序的耦合度:我的目的是给tableView添加一个功能,并且是纯粹的添加功能,不需要新类,所以这种情况应该用category。想想MJRefresh,使用多么简单方便,用过一次就难以忘记。


2.连逻辑都繁琐不堪


这是使用方法:


  // 展示无数据占位图

  [self.tableView showEmptyViewWithType:NoContentTypeNetwork];

  // 无数据占位图点击的回调

  self.tableView.noContentViewTapedBlock = ^{

      [SVProgressHUD showSuccessWithStatus:@"没网"];

  };


  // 移除无数据占位图

  [self.tableView removeEmptyView];


如此不堪的使用方法,甚至还要手动移除占位图。


优化


之前只是针对UITableView占位图的封装,这次面向UIView


曾经的问题就是现在要解决的问题,在这之前,先将思路理一理:


封装这个view

1.至少需要一个参数type来表示是哪种类型的占位图:没网or空数据等等;

2.需要一个回调来处理用户点击重新加载按钮事件。

3.每次展示的占位图只可能有一个。


代码


这是基于UIView的category:


@interface UIView ()


/** 占位图 */

@property (nonatomic, strong) UIView *cq_placeholderView;


@end


@implementation UIView (PlaceholderView)


static void *strKey = &strKey;


- (UIView *)cq_placeholderView {

    return objc_getAssociatedObject(self, &strKey);

}


- (void)setCq_placeholderView:(UIView *)cq_placeholderView {

    objc_setAssociatedObject(self, &strKey, cq_placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}


/**

 展示UIView及其子类的占位图

 

 @param type 占位图类型

 @param reloadBlock 重新加载回调的block

 */

- (void)cq_showPlaceholderViewWithType:(CQPlaceholderViewType)type reloadBlock:(void (^)())reloadBlock {

    // 如果是UIScrollView及其子类,占位图展示期间禁止scroll

    BOOL originalScrollEnabled = NO; // 原本的scrollEnabled

    if ([self isKindOfClass:[UIScrollView class]]) {

        UIScrollView *scrollView = (UIScrollView *)self;

        // 先记录原本的scrollEnabled

        originalScrollEnabled = scrollView.scrollEnabled;

        // 再将scrollEnabled设为NO

        scrollView.scrollEnabled = NO;

    }

    

    //------- 占位图 -------//

    if (self.cq_placeholderView) {

        [self.cq_placeholderView removeFromSuperview];

        self.cq_placeholderView = nil;

    }

    self.cq_placeholderView = [[UIView alloc] init];

    [self addSubview:self.cq_placeholderView];

    self.cq_placeholderView.backgroundColor = [UIColor whiteColor];

    [self.cq_placeholderView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.center.mas_equalTo(self);

        make.size.mas_equalTo(self);

    }];

    

    //------- 图标 -------//

    UIImageView *imageView = [[UIImageView alloc] init];

    [self.cq_placeholderView addSubview:imageView];

    [imageView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(imageView.superview);

        make.centerY.mas_equalTo(imageView.superview).mas_offset(-80);

        make.size.mas_equalTo(CGSizeMake(70, 70));

    }];

    

    //------- 描述 -------//

    UILabel *descLabel = [[UILabel alloc] init];

    [self.cq_placeholderView addSubview:descLabel];

    [descLabel mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(descLabel.superview);

        make.top.mas_equalTo(imageView.mas_bottom).mas_offset(20);

        make.height.mas_equalTo(15);

    }];

    

    //------- 重新加载button -------//

    UIButton *reloadButton = [[UIButton alloc] init];

    [self.cq_placeholderView addSubview:reloadButton];

    [reloadButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];

    [reloadButton setTitle:@"重新加载" forState:UIControlStateNormal];

    reloadButton.layer.borderWidth = 1;

    reloadButton.layer.borderColor = [UIColor blackColor].CGColor;

    [[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {

        // 执行block回调

        if (reloadBlock) {

            reloadBlock();

        }

        // 从父视图移除

        [self.cq_placeholderView removeFromSuperview];

        self.cq_placeholderView = nil;

        // 复原UIScrollView的scrollEnabled

        if ([self isKindOfClass:[UIScrollView class]]) {

            UIScrollView *scrollView = (UIScrollView *)self;

            scrollView.scrollEnabled = originalScrollEnabled;

        }

    }];

    [reloadButton mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerX.mas_equalTo(reloadButton.superview);

        make.top.mas_equalTo(descLabel.mas_bottom).mas_offset(20);

        make.size.mas_equalTo(CGSizeMake(120, 30));

    }];

    

    //------- 根据type设置不同UI -------//

    switch (type) {

        case CQPlaceholderViewTypeNoNetwork: // 网络不好

        {

            NSString *path = [[NSBundle mainBundle] pathForResource:@"无网" ofType:@"png"];

            imageView.image = [UIImage imageWithContentsOfFile:path];

            descLabel.text = @"网络异常";

        }

            break;

            

        case CQPlaceholderViewTypeNoGoods: // 没商品

        {

            NSString *path = [[NSBundle mainBundle] pathForResource:@"无商品" ofType:@"png"];

            imageView.image = [UIImage imageWithContentsOfFile:path];

            descLabel.text = @"一个商品都没有";

        }

            break;

            

        case CQPlaceholderViewTypeNoComment: // 没评论

        {

            NSString *path = [[NSBundle mainBundle] pathForResource:@"沙发" ofType:@"png"];

            imageView.image = [UIImage imageWithContentsOfFile:path];

            descLabel.text = @"抢沙发!";

        }

            break;

            

        default:

            break;

    }

}


@end


使用方法


需要展示占位图的时候直接调用方法:


[self.tableView cq_showPlaceholderViewWithType:CQPlaceholderViewTypeNoComment reloadBlock:^{

    // 按钮点击回调

    [SVProgressHUD showInfoWithStatus:@"重新加载按钮点击"];

}];


细节


1. 对于UIScrollView及其子类,占位图展示期间将它的scrollEnabled设置为NO。


iOS开发造* | UIView及其子类的占位图

这肯定不是我们想要的效果.gif


但是改变scrollEnabled的时候又不能对原控件造成侵入性,怎么破?


  • 先记录最初的scrollEnabled


// 如果是UIScrollView及其子类,占位图展示期间禁止scroll

BOOL originalScrollEnabled = NO; // 原本的scrollEnabled

if ([self isKindOfClass:[UIScrollView class]]) {

    UIScrollView *scrollView = (UIScrollView *)self;

    // 先记录原本的scrollEnabled

    originalScrollEnabled = scrollView.scrollEnabled;

    // 再将scrollEnabled设为NO

    scrollView.scrollEnabled = NO;

}


  • 移除占位图的时候复原UIScrollView的scrollEnabled


// 复原UIScrollView的scrollEnabled

if ([self isKindOfClass:[UIScrollView class]]) {

    UIScrollView *scrollView = (UIScrollView *)self;

    scrollView.scrollEnabled = originalScrollEnabled;

}


2. 给系统类扩展方法或添加属性都要加上前缀,向SDWebImage学习。


// SDWebImage的方法

[imageView sd_setImageWithURL:nil completed:nil];


3. 图片的加载


不需要一直放在内存里的用imageWithContentsofFile:方法。


4. block


不需要写weakSelf:


[self.view cq_showPlaceholderViewWithType:CQPlaceholderViewTypeNoNetwork reloadBlock:^{

    [SVProgressHUD showSuccessWithStatus:@"有网了"];

    // 直接写self也不会导致内存泄漏

    self.view.backgroundColor = [UIColor redColor];

}];


这里的block是局部变量,跟masonry的block是同一个道理:


- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

    self.translatesAutoresizingMaskIntoConstraints = NO;

    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];

    block(constraintMaker);

    return [constraintMaker install];

}


总结


只有不断的反思和总结才能造出更优雅的*。


demo(https://github.com/CaiWanFeng/UIView-PlaceholderView)


iOS开发造* | UIView及其子类的占位图

  • 作者:语歌

  • 链接:https://juejin.im/post/59b97670f265da064b46d617

  • iOS开发整理发布,转载请联系作者授权

iOS开发造* | UIView及其子类的占位图

iOS开发造* | UIView及其子类的占位图【击成为安卓大神】