这是要封装的
这里展示的仅仅是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。
但是改变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
2017年10月2日更新
根据评论区Jacob_Liang同学的建议,优化了部分代码,修复了内存泄漏问题,最新代码已更新到GitHub。感谢Jacob_Liang同学及时指出问题。
2017年10月7日更新
根据评论区S_Criminal_7a03同学的建议,新增一个指定占位图frame的方法:
/**
展示UIView及其子类的占位图,大小可以设置(本质是在这个view上添加一个自定义view)
@param frame 占位图的frame
@param type 占位图类型
@param reloadBlock 重新加载按钮点击时的回调
*/
- (void)cq_showPlaceholderViewWithFrame:(CGRect)frame type:(CQPlaceholderViewType)type reloadBlock:(void (^)())reloadBlock;
代码已合并到GitHub,感谢S_Criminal_7a03同学的提议。
2017年10月12日更新
iPhone X的出现断了frame最后的退路,所以我决定使用纯自动布局了。
默认情况下占位图的约束
@weakify(self);
[self.view cq_showPlaceholderViewWithType:CQPlaceholderViewTypeNoNetwork reloadBlock:^{
@strongify(self);
[SVProgressHUD showSuccessWithStatus:@"重新加载按钮点击"];
self.view.backgroundColor = [UIColor redColor];
}];
可重新设置占位图的约束
[self.tableView cq_showPlaceholderViewWithType:CQPlaceholderViewTypeNoComment reloadBlock:nil];
self.tableView.cq_placeholderView.backgroundColor = [UIColor redColor];
// 重新设置占位图约束
[self.tableView.cq_placeholderView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(self.view);
make.top.mas_equalTo(self.tableView).mas_offset(50);
make.bottom.mas_equalTo(self.view).mas_offset(-30);
}];
代码已更新到GitHub