一、介绍

UITableView和UICollectionView是iOS开发最常用的控件,也是必不可少的控件,这两个控件基本能实现各种各样的界面样式。

它们都是通过代理模式监测数据源的有无对数据进行UI的展示或隐藏。

如果没有数据时,UITableView和UICollectionView可能会展示了一个空白的页面,没有任何提示,逻辑上是没有问题的,但是对于用户而言,显得不够友好。

此时,最好做一个优化,也即没有数据时,刷新列表后提供一个缺省页。

给UITableView和UICollectionView添加一个缺省页,实现的方式有很多种,在这里,我首推采用运行时的hook技术来实现,第三方框架DZNEmptyDataSet就是用这个技术。

本文以UITableView为例,UICollectionView实现原理相同。

二、思路

(1)自定义一个缺省页视图NoDataEmptyView,添加图片视图UIImageView和提示文字标签label;

(2)给UITableView创建一个分类UITableView (ReloadData);

(3)在UITableView (ReloadData)分类中的load方法中使用hook技术用自定义的xyq_reloadData方法交换系统的reloadData方法;

(4)在UITableView (ReloadData)分类中使用关联对象的技术关联缺省页视图NoDataEmptyView对象,也即作为属性;

(5)在UITableView (ReloadData)分类中的xyq_reloadData方法中添加缺省页显示或隐藏的逻辑;

(6)在ViewController中刷新数据时正常调用reloadData方法即可达到实现。

三、代码

NoDataEmptyView

//  NoDataEmptyView.h
// 运行时
//
// Created by 夏远全 on 2019/10/11.
// Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
// #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface NoDataEmptyView : UIView @end NS_ASSUME_NONNULL_END
//
// NoDataEmptyView.m
// 运行时
//
// Created by 夏远全 on 2019/10/11.
// Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
// #import "NoDataEmptyView.h" @interface NoDataEmptyView ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *label;
@end @implementation NoDataEmptyView -(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
} -(void)setup
{
self.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.6];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(, , , )];
self.imageView.image = [UIImage imageNamed:@"empty_body_kong"];
self.imageView.center = self.center; self.label = [[UILabel alloc] initWithFrame:CGRectMake(, , , )];
self.label.text = @"暂无数据";
self.label.textColor = [UIColor whiteColor];
self.label.textAlignment = NSTextAlignmentCenter;
self.label.center = CGPointMake(self.imageView.center.x, CGRectGetMaxY(self.imageView.frame)+); [self addSubview:self.imageView];
[self addSubview:self.label];
} @end

UITableView (ReloadData)

//
// UITableView+ReloadData.h
// 运行时
//
// Created by 夏远全 on 2019/10/11.
// Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
// #import <UIKit/UIKit.h>
#import "NoDataEmptyView.h" NS_ASSUME_NONNULL_BEGIN @interface UITableView (ReloadData)
@property (nonatomic, strong) NoDataEmptyView *emptyView;
@end NS_ASSUME_NONNULL_END
//
// UITableView+ReloadData.m
// 运行时
//
// Created by 夏远全 on 2019/10/11.
// Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
// #import "UITableView+ReloadData.h"
#import <objc/message.h> static NSString *const NoDataEmptyViewKey = @"NoDataEmptyViewKey"; @implementation UITableView (ReloadData) #pragma mark - 交换方法 hook
+(void)load {
Method originMethod = class_getInstanceMethod(self, @selector(reloadData));
Method currentMethod = class_getInstanceMethod(self, @selector(xyq_reloadData));
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(originMethod, currentMethod);
});
} #pragma mark - 刷新数据
-(void)xyq_reloadData {
[self xyq_reloadData];
[self fillEmptyView];
} #pragma mark - 填充空白页
-(void)fillEmptyView { NSInteger sections = ;
NSInteger rows = ; id <UITableViewDataSource> dataSource = self.dataSource; if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { sections = [dataSource numberOfSectionsInTableView:self]; if ([dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
for (int i=; i<sections; i++) {
rows += [dataSource tableView:self numberOfRowsInSection:i];
}
}
} if (rows == ) {
if (![self.subviews containsObject:self.emptyView]) {
self.emptyView = [[NoDataEmptyView alloc] initWithFrame:self.bounds];
[self addSubview:self.emptyView];
}
}
else{
[self.emptyView removeFromSuperview];
}
} #pragma mark - 关联对象
-(void)setEmptyView:(NoDataEmptyView *)emptyView {
objc_setAssociatedObject(self, &NoDataEmptyViewKey, emptyView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} -(NoDataEmptyView *)emptyView {
return objc_getAssociatedObject(self, &NoDataEmptyViewKey);
} @end

DataTableViewController

//
// DataTableViewController.m
// 运行时
//
// Created by 夏远全 on 2019/10/11.
// Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
// #import "DataTableViewController.h"
#import "UITableView+ReloadData.h" @interface DataTableViewController ()<UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UIButton *haveDataBtn;
@property (nonatomic, strong) UIButton *clearDataBtn;
@property (nonatomic, strong) NSMutableArray *dataSource;
@end @implementation DataTableViewController #pragma mark - life cycle - (void)viewDidLoad
{
[super viewDidLoad];
[self setupNavigation];
[self setupSubviews];
} -(void)setupNavigation
{
self.title = @"测试空白页";
self.view.backgroundColor = [UIColor whiteColor];
} -(void)setupSubviews
{
[self.view addSubview:self.haveDataBtn];
[self.view addSubview:self.clearDataBtn];
[self.view addSubview:self.tableView];
} #pragma mark - dataSource methods
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return ;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataSource.count;
} -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *reuserIdentifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuserIdentifier];
}
cell.textLabel.text = self.dataSource[indexPath.row];
return cell;
} #pragma mark - event
-(void)haveDataBtnAction {
self.dataSource = @[@"第1行数据",@"第2行数据",@"第3行数据",@"第4行数据",@"第5行数据",@"第6行数据"].mutableCopy;
[self.tableView reloadData]; //内部调用自己的xyq_reloadData
} -(void)clearDataBtnAction {
[self.dataSource removeAllObjects];
[self.tableView reloadData]; //内部调用自己的xyq_reloadData
} #pragma mark - getters
-(UITableView *)tableView {
if (!_tableView) {
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat height = [UIScreen mainScreen].bounds.size.height;
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(, +, width, height--)];
_tableView.backgroundColor = [UIColor whiteColor];
_tableView.tableFooterView = [[UIView alloc] init];
_tableView.dataSource = self;
}
return _tableView;
} -(UIButton *)haveDataBtn {
if (!_haveDataBtn) {
CGFloat width = [UIScreen mainScreen].bounds.size.width;
_haveDataBtn = [[UIButton alloc] initWithFrame:CGRectMake(, +, width/-, )];
_haveDataBtn.backgroundColor = [UIColor redColor];
[_haveDataBtn setTitle:@"显示数据" forState:UIControlStateNormal];
[_haveDataBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_haveDataBtn addTarget:self action:@selector(haveDataBtnAction) forControlEvents:UIControlEventTouchUpInside];
}
return _haveDataBtn;
} -(UIButton *)clearDataBtn {
if (!_clearDataBtn) {
CGFloat width = [UIScreen mainScreen].bounds.size.width;
_clearDataBtn = [[UIButton alloc] initWithFrame:CGRectMake(width/+, +, width/-, )];
_clearDataBtn.backgroundColor = [UIColor purpleColor];
[_clearDataBtn setTitle:@"清空数据" forState:UIControlStateNormal];
[_clearDataBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_clearDataBtn addTarget:self action:@selector(clearDataBtnAction) forControlEvents:UIControlEventTouchUpInside];
}
return _clearDataBtn;
} @end 

四、演示

使用Runtime的hook技术为tableView实现一个空白缺省页的更多相关文章

  1. HOOK技术的一些简单总结

    好久没写博客了, 一个月一篇还是要尽量保证,今天谈下Hook技术. 在Window平台上开发任何稍微底层一点的东西,基本上都是Hook满天飞, 普通应用程序如此,安全软件更是如此, 这里简单记录一些常 ...

  2. Hook技术

    hook钩子: 使用技术手段在运行时动态的将额外代码依附现进程,从而实现替换现有处理逻辑或插入额外功能的目的. 它的技术实现要点有两个: 1)如何注入代码(如何将额外代码依附于现有代码中). 2)如何 ...

  3. 程序破解之 API HOOK技术 z

    API HOOK,就是截获API调用的技术,在程序对一个API调用之前先执行你的函数,然后根据你的需要可以执行缺省的API调用或者进行其他处理,假设如果想截获一个进程对网络的访问,一般是几个socke ...

  4. API HOOK技术

    API HOOK技术是一种用于改变API执行结果的技术,Microsoft 自身也在Windows操作系统里面使用了这个技术,如Windows兼容模式等. API HOOK 技术并不是计算机病毒专有技 ...

  5. 【Hook技术】实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展

    [Hook技术]实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展 公司有个监控程序涉及到进程的保护问题,需要避免用户通过任务管理器结束掉监控进程,这里使用 ...

  6. 逆向实用干货分享,Hook技术第一讲,之Hook Windows API

    逆向实用干货分享,Hook技术第一讲,之Hook Windows API 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) ...

  7. 逆向实用干货分享,Hook技术第二讲,之虚表HOOK

    逆向实用干货分享,Hook技术第二讲,之虚表HOOK 正好昨天讲到认识C++中虚表指针,以及虚表位置在反汇编中的表达方式,这里就说一下我们的新技术,虚表HOOK 昨天的博客链接: http://www ...

  8. x64内核HOOK技术之拦截进程.拦截线程.拦截模块

    x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么直接讲CR0的内存保护属性去掉. 直接讲表的地址修改即可. 但是 ...

  9. Windows Hook技术

    0x01 简介 有人称它为“钩子”,有人称它为“挂钩”技术.谈到钩子,很容易让人联想到在钓东西,比如鱼钩就用于钓鱼.编程技术的钩子也是在等待捕获系统中的某个消息或者动作.钩子的应用范围非常广泛,比如输 ...

随机推荐

  1. SpringCloud之Eureka:集群搭建

    上篇文章<SpringCloud之Eureka:服务发布与调用例子>实现了一个简单例子,这次对其进行改造,运行两个服务器实例.两个服务提供者实例,服务调用者请求服务,使其可以进行集群部署. ...

  2. celery执行异步任务和定时任务

    一.什么是Clelery Celery是一个简单.灵活且可靠的,处理大量消息的分布式系统 专注于实时处理的异步任务队列 同时也支持任务调度 Celery架构 Celery的架构由三部分组成,消息中间件 ...

  3. Mysql增量备份之Mysqldump&Mylvmbackup

    简单介绍 备份类型 备份方式 热备份:备份期间不需要服务停机,业务不受影响: 温备份:备份期间仅允许读的请求: 冷备份:备份期间需要关闭Mysql服务或读写请求都不受影响: 完全备份:full bac ...

  4. [阅读笔记]EfficientDet

    EfficientDet 文章阅读 Google的网络结构不错,总是会考虑计算性能的问题,从mobilenet v1到mobile net v2.这篇文章主要对近来的FPN结构进行了改进,实现了一种效 ...

  5. numpy函数查询手册

    写了个程序,对Numpy的绝大部分函数及其说明进行了中文翻译. 原网址:https://docs.scipy.org/doc/numpy/reference/routines.html#routine ...

  6. 用dotnet core搭建web服务器(三)ORM访问数据库

    访问传统sql数据库,大家以前都是用sql语句去查询.这些年流行orm方法 ORM是对象关系映射的简拼,就是用一个对象(class)去表示数据的一行,用对象的成员去表述数据的列 dotnet 官方很早 ...

  7. Leetcode题解 - 树部分简单题目代码+思路(105、106、109、112、897、257、872、226、235、129)

    树的题目中递归用的比较多(但是递归是真难弄 我

  8. SpringBoot源码学习系列之@PropertySource不支持yaml读取原因

    然后,为什么@PropertySource注解默认不支持?可以简单跟一下源码 @PropertySource源码: 根据注释,默认使用DefaultPropertySourceFactory类作为资源 ...

  9. 将python项目打包为可运行的windows桌面exe程序

    ---恢复内容开始--- 步骤大概如下: 1.需要一个python文件/项目.也就是我们想要打包的文件 2.安装pyinstaller,目的是将我们的python文件生成为exe可执行程序. 3.使用 ...

  10. linux创建用户并锁定用户目录和首次登陆强制修改密码

    1.     创建用户及访问目录 mkdir -p /home/user/testuser   创建用户目录 useradd testuser -d /home/user/testuser  -M   ...