TableView 无数据时展示占位视图
UITableView+NoDataView.m
#import "UITableView+NoDataView.h"
#import "NoDataView.h"
#import <objc/runtime.h> @protocol TableViewDelegate <NSObject>
@optional
- (UIView *)noDataView;
- (UIImage *)noDataViewImage;
- (NSString *)noDataViewMessage;
- (UIColor *)noDataViewMessageColor;
- (NSNumber *)noDataViewCenterYOffset; @end @implementation UITableView (NoDataView) + (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method reloadData = class_getInstanceMethod(self, @selector(reloadData));
Method replace_reloadData = class_getInstanceMethod(self, @selector(replace_reloadData));
method_exchangeImplementations(reloadData, replace_reloadData); Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
Method replace_dealloc = class_getInstanceMethod(self, @selector(replace_dealloc));
method_exchangeImplementations(dealloc, replace_dealloc);
});
} - (void)replace_reloadData {
[self replace_reloadData]; // 忽略第一次加载
if (![self isInitFinish]) {
[self havingData:YES];
[self setIsInitFinish:YES];
return ;
} // 刷新完成之后检测数据量
dispatch_async(dispatch_get_main_queue(), ^{ NSInteger numberOfSections = [self numberOfSections];
BOOL havingData = NO;
for (NSInteger i = ; i < numberOfSections; i++) {
if ([self numberOfRowsInSection:i] > ) {
havingData = YES;
break;
}
} [self havingData:havingData];
});
} /**
展示占位图
*/
- (void)havingData:(BOOL)havingData { // 不需要显示占位图
if (havingData) {
[self freeNoDataViewIfNeeded];
self.backgroundView = nil;
return ;
} // 不需要重复创建
if (self.backgroundView) {
return ;
} // 自定义了占位图
if ([self.delegate respondsToSelector:@selector(noDataView)]) {
self.backgroundView = [self.delegate performSelector:@selector(noDataView)];
return ;
} // 使用自带的
UIImage * img = nil;
NSString * msg = @"暂无数据";
UIColor * color = [UIColor lightGrayColor];
CGFloat offset = ; // 获取图片
if ([self.delegate respondsToSelector:@selector(noDataViewImage)]) {
img = [self.delegate performSelector:@selector(noDataViewImage)];
}
// 获取文字
if ([self.delegate respondsToSelector:@selector(noDataViewMessage)]) {
msg = [self.delegate performSelector:@selector(noDataViewMessage)];
}
// 获取颜色
if ([self.delegate respondsToSelector:@selector(noDataViewMessageColor)]) {
color = [self.delegate performSelector:@selector(noDataViewMessageColor)];
}
// 获取偏移量
if ([self.delegate respondsToSelector:@selector(noDataViewCenterYOffset)]) {
offset = [[self.delegate performSelector:@selector(noDataViewCenterYOffset)] floatValue];
} // 创建占位图
self.backgroundView = [self defaultNoDataViewWithImage :img message:msg color:color offsetY:offset];
} /**
默认的占位图
*/
- (UIView *)defaultNoDataViewWithImage:(UIImage *)image message:(NSString *)message color:(UIColor *)color offsetY:(CGFloat)offset { // 计算位置, 垂直居中, 图片默认中心偏上.
CGFloat sW = self.bounds.size.width;
CGFloat cX = sW / ;
CGFloat cY = self.bounds.size.height * ( - 0.618) + offset;
CGFloat iW = image.size.width;
CGFloat iH = image.size.height; // 图片
UIImageView *imgView = [[UIImageView alloc] init];
imgView.frame = CGRectMake(cX - iW / , cY - iH / , iW, iH);
imgView.image = image; // 文字
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:];
label.textColor = color;
label.text = message;
label.textAlignment = NSTextAlignmentCenter;
label.frame = CGRectMake(, CGRectGetMaxY(imgView.frame) + , sW, label.font.lineHeight); // 视图
NoDataView *view = [[NoDataView alloc] init];
[view addSubview:imgView];
[view addSubview:label]; // 实现跟随 TableView 滚动
[view addObserver:self forKeyPath:kNoDataViewObserveKeyPath options:NSKeyValueObservingOptionNew context:nil];
return view;
} /**
监听
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:kNoDataViewObserveKeyPath]) { /**
在 TableView 滚动 ContentOffset 改变时, 会同步改变 backgroundView 的 frame.origin.y
可以实现, backgroundView 位置相对于 TableView 不动, 但是我们希望
backgroundView 跟随 TableView 的滚动而滚动, 只能强制设置 frame.origin.y 永远为 0
兼容 MJRefresh
*/
CGRect frame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue];
if (frame.origin.y != ) {
frame.origin.y = ;
self.backgroundView.frame = frame;
}
}
} #pragma mark - 属性 // 加载完数据的标记属性名
static NSString * const kTableViewPropertyInitFinish = @"kTableViewPropertyInitFinish"; /**
设置已经加载完成数据了
*/
- (void)setIsInitFinish:(BOOL)finish {
objc_setAssociatedObject(self, &kTableViewPropertyInitFinish, @(finish), OBJC_ASSOCIATION_ASSIGN);
} /**
是否已经加载完成数据
*/
- (BOOL)isInitFinish {
id obj = objc_getAssociatedObject(self, &kTableViewPropertyInitFinish);
return [obj boolValue];
} /**
移除 KVO 监听
*/
- (void)freeNoDataViewIfNeeded { if ([self.backgroundView isKindOfClass:[NoDataView class]]) {
[self.backgroundView removeObserver:self forKeyPath:kNoDataViewObserveKeyPath context:nil];
}
} - (void)replace_dealloc {
[self freeNoDataViewIfNeeded];
[self replace_dealloc];
NSLog(@"TableView 视图正常销毁");
} @end
UICollectionView+NoDataView.m
#import "UICollectionView+NoDataView.h"
#import <objc/runtime.h>
#import "NoDataView.h" /**
消除警告
*/
@protocol CollectionViewDelegate <NSObject>
@optional
- (UIView *)noDataView;
- (UIImage *)noDataViewImage;
- (NSString *)noDataViewMessage;
- (UIColor *)noDataViewMessageColor;
- (NSNumber *)noDataViewCenterYOffset;
@end @implementation UICollectionView (NoDataView)
/**
加载时, 交换方法
*/
+ (void)load {
// 只交换一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ Method reloadData = class_getInstanceMethod(self, @selector(reloadData));
Method replace_reloadData = class_getInstanceMethod(self, @selector(replace_reloadData));
method_exchangeImplementations(reloadData, replace_reloadData); Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
Method replace_dealloc = class_getInstanceMethod(self, @selector(replace_dealloc));
method_exchangeImplementations(dealloc, replace_dealloc);
});
} /**
在 ReloadData 的时候检查数据
*/
- (void)replace_reloadData { [self replace_reloadData]; // 忽略第一次加载
if (![self isInitFinish]) {
[self havingData:YES];
[self setIsInitFinish:YES];
return ;
}
// 刷新完成之后检测数据量
dispatch_async(dispatch_get_main_queue(), ^{ NSInteger numberOfSections = [self numberOfSections];
BOOL havingData = NO;
for (NSInteger i = ; i < numberOfSections; i++) {
if ([self numberOfItemsInSection:i] > ) {
havingData = YES;
break;
}
} [self havingData:havingData];
});
} /**
展示占位图
*/
- (void)havingData:(BOOL)havingData { // 不需要显示占位图
if (havingData) {
[self freeNoDataViewIfNeeded];
self.backgroundView = nil;
return ;
} // 不需要重复创建
if (self.backgroundView) {
return ;
} // 自定义了占位图
if ([self.delegate respondsToSelector:@selector(noDataView)]) {
self.backgroundView = [self.delegate performSelector:@selector(noDataView)];
return ;
} // 使用自带的
UIImage *img = nil;
NSString *msg = @"暂无数据";
UIColor *color = [UIColor lightGrayColor];
CGFloat offset = ; // 获取图片
if ([self.delegate respondsToSelector:@selector(noDataViewImage)]) {
img = [self.delegate performSelector:@selector(noDataViewImage)];
}
// 获取文字
if ([self.delegate respondsToSelector:@selector(noDataViewMessage)]) {
msg = [self.delegate performSelector:@selector(noDataViewMessage)];
}
// 获取颜色
if ([self.delegate respondsToSelector:@selector(noDataViewMessageColor)]) {
color = [self.delegate performSelector:@selector(noDataViewMessageColor)];
}
// 获取偏移量
if ([self.delegate respondsToSelector:@selector(noDataViewCenterYOffset)]) {
offset = [[self.delegate performSelector:@selector(noDataViewCenterYOffset)] floatValue];
} // 创建占位图
self.backgroundView = [self defaultNoDataViewWithImage :img message:msg color:color offsetY:offset];
} /**
默认的占位图
*/
- (UIView *)defaultNoDataViewWithImage:(UIImage *)image message:(NSString *)message color:(UIColor *)color offsetY:(CGFloat)offset { // 计算位置, 垂直居中, 图片默认中心偏上.
CGFloat sW = self.bounds.size.width;
CGFloat cX = sW / ;
CGFloat cY = self.bounds.size.height * ( - 0.618) + offset;
CGFloat iW = image.size.width;
CGFloat iH = image.size.height; // 图片
UIImageView *imgView = [[UIImageView alloc] init];
imgView.frame = CGRectMake(cX - iW / , cY - iH / , iW, iH);
imgView.image = image; // 文字
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:];
label.textColor = color;
label.text = message;
label.textAlignment = NSTextAlignmentCenter;
label.frame = CGRectMake(, CGRectGetMaxY(imgView.frame) + , sW, label.font.lineHeight); // 视图
NoDataView * view = [[NoDataView alloc] init];
[view addSubview:imgView];
[view addSubview:label]; // 实现跟随 collectionView 滚动
[view addObserver:self forKeyPath:kNoDataViewObserveKeyPath options:NSKeyValueObservingOptionNew context:nil];
return view;
} /**
监听
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:kNoDataViewObserveKeyPath]) { /**
在 collectionView 滚动 ContentOffset 改变时, 会同步改变 backgroundView 的 frame.origin.y
可以实现, backgroundView 位置相对于 collectionView 不动, 但是我们希望
backgroundView 跟随 collectionView 的滚动而滚动, 只能强制设置 frame.origin.y 永远为 0
兼容 MJRefresh
*/
CGRect frame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue];
if (frame.origin.y != ) {
frame.origin.y = ;
self.backgroundView.frame = frame;
}
}
} #pragma mark - 属性 /// 加载完数据的标记属性名
static NSString * const kCollectionViewPropertyInitFinish = @"kCollectionViewPropertyInitFinish"; /**
设置已经加载完成数据了
*/
- (void)setIsInitFinish:(BOOL)finish {
objc_setAssociatedObject(self, &kCollectionViewPropertyInitFinish, @(finish), OBJC_ASSOCIATION_ASSIGN);
} /**
是否已经加载完成数据
*/
- (BOOL)isInitFinish {
id obj = objc_getAssociatedObject(self, &kCollectionViewPropertyInitFinish);
return [obj boolValue];
} /**
移除 KVO 监听
*/
- (void)freeNoDataViewIfNeeded { if ([self.backgroundView isKindOfClass:[NoDataView class]]) {
[self.backgroundView removeObserver:self forKeyPath:kNoDataViewObserveKeyPath context:nil];
}
} - (void)replace_dealloc {
[self freeNoDataViewIfNeeded];
[self replace_dealloc];
NSLog(@"CollectionView 视图正常销毁");
}
@end
NoDataView.h
#import <UIKit/UIKit.h> extern NSString * const kNoDataViewObserveKeyPath; @interface NoDataView : UIView @end
NoDataView.m
#import "NoDataView.h"
NSString * const kNoDataViewObserveKeyPath = @"frame";
@implementation NoDataView - (void)dealloc {
NSLog(@"占位视图正常销毁");
} @end
调用
#import "ViewController.h"
#import "MJRefresh.h" @interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView * tableView;
@property (nonatomic, strong) NSMutableArray * dataArr;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
self.tableView.tableFooterView = [UIView new]; __weak typeof(self) weakSelf = self;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[weakSelf loadData];
}];
} - (void)loadData {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.tableView.mj_header endRefreshing];
[self.tableView reloadData];
});
} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return ;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [UITableViewCell new];
} #pragma mark - TableView 占位图 - (UIImage *)noDataViewImage {
return [UIImage imageNamed:@"note_list_no_data"];
} - (NSString *)noDataViewMessage {
return @"都用起来吧, 起飞~";
} - (UIColor *)noDataViewMessageColor {
return [UIColor blackColor];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end
TableView 无数据时展示占位视图的更多相关文章
- IOS-当遇到tableView整体上移时的解决方案
方案一在使用了navigationController后,当界面进行跳转往返后,时而会出现tableView或collectionView上移的情况,通常会自动上移64个像素,那么这种情况,我们可以关 ...
- IOS开发-当遇到tableView整体上移时的解决方案
方案一在使用了navigationController后,当界面进行跳转往返后,时而会出现tableView上移的情况,通常会自动上移64个像素,那么这种情况,我们可以关闭tableView的自动适配 ...
- iOS开发之--当遇到tableView整体上移时的解决方案
方案一在使用了navigationController后,当界面进行跳转往返后,时而会出现tableView上移的情况,通常会自动上移64个像素,那么这种情况,我们可以关闭tableView的自动适配 ...
- iOS UIScrollView 滚动到当前展示的视图居中展示
需求展示: 测试效果1 first uiscrollView 宽度 为屏幕宽度 滚动步长 为 scroll 宽度的1/3 分析: 这个是最普通版 无法使每一次滚动的结果子视图居中展示, WA ...
- 看代码学知识之(2) ListView无数据时显示其他View
看代码学知识之(2) ListView无数据时显示其他View 今天看的一块布局是这样的: <!-- The frame layout is here since we will be show ...
- 父视图 使用 UIViewAnimationWithBlocks 时,如何让子视图无动画
tableView使用 UIViewAnimationWithBlocks 时 上面的cell也会一起出现动画, 所以在设置cell的时候 添加 [UIView performWithoutAnima ...
- 刷新SQL Server所有视图、函数、存储过程 更多 sql 此脚本用于在删除或添加字段时刷新相关视图,并检查视图、函数、存储过程有效性。 [SQL]代码 --视图、存储过程、函数名称 DECLARE @NAME NVARCHAR(255); --局部游标 DECLARE @CUR CURSOR --自动修改未上状态为旷课 SET @CUR=CURSOR SCROLL DYNAMIC FO
刷新SQL Server所有视图.函数.存储过程 更多 sql 此脚本用于在删除或添加字段时刷新相关视图,并检查视图.函数.存储过程有效性. [SQL]代码 --视图.存储过程.函数名称 DE ...
- [转帖]Oracle报错ORA-26563--当重命名表时碰到物化视图
Oracle报错ORA-26563--当重命名表时碰到物化视图 https://www.toutiao.com/i6739137279115133447/ 原创 波波说运维 2019-09-26 00 ...
- tableView 选中cell时,获取到当前cell
// >> 找到当前选中的cell,设置选中时的cell背景色 SideTableViewCell * cell = (SideTableViewCell *)[tableView cel ...
随机推荐
- Spring中 PROPAGATION_REQUIRED 解释 事物是在一个方法里调用其他的方法,一起成功或者一起失败,是方法之间的关系,而不是某一个方法内部的问题。而且要以抛异常的方式来表明方法的失败,以此来导致事物起作用,大家全失败。
事务传播行为种类 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为, 它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播: 事务传播行为类型 事务传播 ...
- 3-6 Vue中的条件渲染
本次案例讲解:v-if,v-show,v-else,v-else-if和key值的相关用法!!! v-if指令: //通过一个(v-if)指令 ,可以结合js表达式的返回值,决定一个标签是否在页面上展 ...
- 从输入 URL 到页面加载完成的过程详解---【XUEBIG】
从输入 URL 到页面加载完成的过程中都发生了什么事情? 这是一道经典的面试题,涉及面非常广,要答出来并不困难,当要将问题回答好却不是那么容易 过程概述 浏览器查找域名对应的 IP 地址: 浏览器根据 ...
- 10个非常有趣的Linux命令【转载】
Linux当中有很多比较有趣的命令,可以动手看看,很简单的. 1.rev命令 一行接一行地颠倒所输入的字符串. 运行: $rev 如输入:shiyanlou shiyanlou rev.jpg 2.a ...
- LanProxy 内网映射穿透
前言:用过 ngrok 的人都知道,这是一个免费并且简便的内网映射工具,可是现在ngrok不知道弄啥?不能用了,那我们只能去找一些新的工具,下面是我跟我朋友一起弄的(主要是他教我(✪ω✪)),免费的, ...
- C#常用字符串函数
Compare 比较字符串的内容,考虑文化背景(场所),确定某些字符是否相等 CompareOrdinal 与Compare一样,但不考虑文化背景 Format 格式化包含各种值的字符串和如何格式化每 ...
- BZOJ.5288.[AHOI/HNOI2018]游戏(思路 拓扑)
BZOJ LOJ 洛谷 考虑如何预处理每个点能到的区间\([l,r]\). 对于\(i,i+1\)的一扇门,如果钥匙在\(i\)的右边,连边\(i\to i+1\),表示从\(i\)出发到不了\(i+ ...
- Django——ModuleNotFoundError: No module named 'asgiref.sync'
Django+channels运行时报错 Unhandled exception in thread started by <function check_errors.<locals&g ...
- BZOJ1482 : [Balkan2017]Cats
若猫和狗中至少有一个出现了$0$次,那么答案显然是$0$,否则若狮子出现了$0$次,那么显然无解. 那么现在至少有一个动物保持原地不同,其它动物恰好移动一次. 如果全部猫都不动而全部狗都动,那么可以贪 ...
- [P3452][POI2007]BIU-Offices (BFS)
这里有一个很完美(搞笑但是确实是这样的)翻译 题意 神牛 LXX 昨天刚刚满 18 岁,他现在是个成熟的有为男青年.他有 N 个 MM,分别从 1 到 N 标号. 这些 MM 有些是互相认识的.现在, ...