runtime入侵实现tableview自动显示隐藏无数据占位图
在我们开发中,tableview和collectionview经常被用来展示数据列表。一般来说,tableView获取到的数据源为空时,直接展示一个空的tableView显得比较突兀,所以设计师往往会针对这种情况给出相应的UI,用来替代空tableView的展示。无数据占位图,就是当后台返回的数据源为空时需要展示的页面
如果只是想实现这个效果,有非常多的方法。
下面介绍一种利用runtime methodchange来实现自动控制的方法,使用非常简单便捷,只需要import文件,进行属性控制就可以了。避免繁琐的手动控制占位图显示隐藏
使用示例:一般app的占位视图都是一致的 不需要每个tableview都去重复创建
建议占位视图根据项目情况单独封装一个uiview
或者把默认的view写到分类代码里边儿,就不用调用下面的set方法了
//自动控制占位图
self.tableView.autoShowNoData = YES;
//设置占位图
//一般app的占位视图都是一致的 不需要每个tableview都去重复创建
//建议占位视图根据项目情况单独封装一个uiview
//或者把默认的view写到分类代码里边儿,就不用调用下面的方法了
self.tableView.nodataView = ({
UILabel *label = [[UILabel alloc] init];
label.text = @"暂无内容";
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor blackColor];
label.backgroundColor = [UIColor groupTableViewBackgroundColor];
label;
});
实现思路
1.占位图设置并保存
2.提供展示和隐藏方法
3.拦截数据变化
4.判断是否有数据自动调用展示隐藏方法
1.占位图设置并保存
初始化一个视图来做为占位,这个很简单,根据项目UI设计的来做就行了。那怎么保存呢,用tableview自身去保存占位图对象,为什么呢?因为如果这一步使用了其他的对象去保存占位图,会导致代码耦合性增大。要做好一个封装,尽量降低耦合,所有这里不引入其他对象了。
tableview没有占位视图这个属性,那么我们就给它动态增加一个nodataView
创建一个分类 增加属性
@interface UITableView (NoData)
//可定制的无数据视图 默认为显示暂无数据label
//居中显示
@property(nonatomic,strong)UIView *nodataView;
//自动显示隐藏无数据视图 根据协议返回的section个数以及cell个数联合判断 (默认为NO,需要手动调用showNoDataView显示)
@property(nonatomic,assign)BOOL autoShowNoData;
@end
.m文件实现setter gtter
- (UIView *)nodataView
{
return objc_getAssociatedObject(self, @selector(nodataView));
}
- (void)setNodataView:(UIView *)nodataView{
objc_setAssociatedObject(self, @selector(nodataView), nodataView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setAutoShowNoData:(BOOL)autoShowNoData{
objc_setAssociatedObject(self, @selector(autoShowNoData), @(autoShowNoData), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self reloadData];
}
- (BOOL)autoShowNoData{
return [objc_getAssociatedObject(self, @selector(autoShowNoData)) boolValue];
}
2.提供展示和隐藏方法
提供方法并配置默认占位视图 默认占位图根据项目情况设置,这个很重要,可以减少业务层代码
- (void)showNoDataView
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self removeNoDataView];
if (!self.nodataView) {
UILabel *label = [[UILabel alloc] init];
label.text = @"暂无内容";
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor hexColor:@"363636"];
self.nodataView = label;
}
[self addSubview:self.nodataView];
[self.nodataView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(@0);
make.centerY.equalTo(@0);
}];
});
}
- (void)removeNoDataView
{
[self.nodataView removeFromSuperview];
}
3.拦截数据变化事件
如何拦截到每一次数据更新?由于tableview的数据显示变化都是由reloadData方法导致的,所以我们想办法监听这个方法的执行。可以用切面编程的思想,Aspects这个框架可以做到,非常好用。mjrefresh的mj_reloadBlock也可以做到,但是我们要尽量减少依赖,这里我们可以使用 <objc/runtime.h>的method_exchangeImplementations函数进行方法替换
method_exchangeImplementations 拦截reloadData
+ (void)load{
Method reloadData = class_getInstanceMethod(self, @selector(reloadData));
Method wy_reloadData = class_getInstanceMethod(self, @selector(wy_reloadData));
method_exchangeImplementations(reloadData, wy_reloadData);
}
- (void)wy_reloadData{
[self wy_reloadData];
[self checkData];
}
4.判断是否有数据自动调用展示隐藏方法
通过上面的步骤我们成功的拦截到了每一次数据变化,接下来就是判断是否有数据了。
- (void)checkData{
if (self.autoShowNoData) {
BOOL haveData = YES;
if (!self.dataSource) {
haveData = NO;
}else{
if ([self.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]&&[self.dataSource numberOfSectionsInCollectionView:self]<=0) {
haveData = NO;
}else{
NSInteger section =[self.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]? [self.dataSource numberOfSectionsInCollectionView:self]:1;
haveData = NO;
for (int i = 0; i<section; i++) {
if ([self.dataSource collectionView:self numberOfItemsInSection:i]>0) {
haveData = YES;
break;
}
}
}
}
if (haveData) {
[self removeNoDataView];
}else{
[self showNoDataView];
}
}
}
这样就完成了tableview的占位控制分类封装。
使用起来是不是很方便啊