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

2017 年 9 月 26 日 CocoaChina 无夜之星辰

IU


这是要封装的



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


封装原因


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


此事无关代码,关乎优雅


然后我翻看了一下自己以前的写的关于封装这个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。


这肯定不是我们想要的效果.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


登录查看更多
0

相关内容

【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
开源书:PyTorch深度学习起步
专知会员服务
50+阅读 · 2019年10月11日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
iOS自定义带动画效果的模态框
CocoaChina
7+阅读 · 2019年3月3日
去哪儿网开源DNS管理系统OpenDnsdb
运维帮
21+阅读 · 2019年1月22日
如何编写完美的 Python 命令行程序?
CSDN
5+阅读 · 2019年1月19日
PyTorch 到底好用在哪里?
AI研习社
3+阅读 · 2017年10月27日
2017 VQA Challenge 第一名技术报告
PaperWeekly
7+阅读 · 2017年9月26日
iOS高级调试&逆向技术
CocoaChina
3+阅读 · 2017年7月30日
Arxiv
10+阅读 · 2020年4月5日
Deep Reinforcement Learning: An Overview
Arxiv
17+阅读 · 2018年11月26日
Arxiv
6+阅读 · 2018年2月8日
Arxiv
9+阅读 · 2016年10月27日
VIP会员
相关VIP内容
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
开源书:PyTorch深度学习起步
专知会员服务
50+阅读 · 2019年10月11日
相关资讯
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
iOS自定义带动画效果的模态框
CocoaChina
7+阅读 · 2019年3月3日
去哪儿网开源DNS管理系统OpenDnsdb
运维帮
21+阅读 · 2019年1月22日
如何编写完美的 Python 命令行程序?
CSDN
5+阅读 · 2019年1月19日
PyTorch 到底好用在哪里?
AI研习社
3+阅读 · 2017年10月27日
2017 VQA Challenge 第一名技术报告
PaperWeekly
7+阅读 · 2017年9月26日
iOS高级调试&逆向技术
CocoaChina
3+阅读 · 2017年7月30日
相关论文
Top
微信扫码咨询专知VIP会员