使用Runtime的hook技术为tableView实现一个空白缺省页
一、介绍
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实现一个空白缺省页的更多相关文章
- HOOK技术的一些简单总结
好久没写博客了, 一个月一篇还是要尽量保证,今天谈下Hook技术. 在Window平台上开发任何稍微底层一点的东西,基本上都是Hook满天飞, 普通应用程序如此,安全软件更是如此, 这里简单记录一些常 ...
- Hook技术
hook钩子: 使用技术手段在运行时动态的将额外代码依附现进程,从而实现替换现有处理逻辑或插入额外功能的目的. 它的技术实现要点有两个: 1)如何注入代码(如何将额外代码依附于现有代码中). 2)如何 ...
- 程序破解之 API HOOK技术 z
API HOOK,就是截获API调用的技术,在程序对一个API调用之前先执行你的函数,然后根据你的需要可以执行缺省的API调用或者进行其他处理,假设如果想截获一个进程对网络的访问,一般是几个socke ...
- API HOOK技术
API HOOK技术是一种用于改变API执行结果的技术,Microsoft 自身也在Windows操作系统里面使用了这个技术,如Windows兼容模式等. API HOOK 技术并不是计算机病毒专有技 ...
- 【Hook技术】实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展
[Hook技术]实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展 公司有个监控程序涉及到进程的保护问题,需要避免用户通过任务管理器结束掉监控进程,这里使用 ...
- 逆向实用干货分享,Hook技术第一讲,之Hook Windows API
逆向实用干货分享,Hook技术第一讲,之Hook Windows API 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) ...
- 逆向实用干货分享,Hook技术第二讲,之虚表HOOK
逆向实用干货分享,Hook技术第二讲,之虚表HOOK 正好昨天讲到认识C++中虚表指针,以及虚表位置在反汇编中的表达方式,这里就说一下我们的新技术,虚表HOOK 昨天的博客链接: http://www ...
- x64内核HOOK技术之拦截进程.拦截线程.拦截模块
x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么直接讲CR0的内存保护属性去掉. 直接讲表的地址修改即可. 但是 ...
- Windows Hook技术
0x01 简介 有人称它为“钩子”,有人称它为“挂钩”技术.谈到钩子,很容易让人联想到在钓东西,比如鱼钩就用于钓鱼.编程技术的钩子也是在等待捕获系统中的某个消息或者动作.钩子的应用范围非常广泛,比如输 ...
随机推荐
- MySQL能否授予查看存储过程定义权限给用户
在其他RDBMS中,可以将查看某个存储过程(PROCEDURE)定义的权限给某个用户,例如在SQL Server中,可以单独将查看ProcedureName定义的权限授予UserA GRANT VIE ...
- Opencv中图像height width X 轴 Y轴 rows cols之间的对应关系
这里做一个备忘录:
- Centos 7.5私有域名服务器部署(coredns+etcd)
单机配置: 一.安装etcd: 1.安装 yum install etcd -y 2.启动 systemctl start etcd 3.设置开机启动 systemctl enable e ...
- 如何在Mac上使用Siri
在您的iPhone上,要求Siri执行命令很简单.但是,如果在计算机上工作时附近没有iPhone,会发生什么情况?您也可以在Mac上使用Siri.快速简便,使其成为iMac或MacBook的完美伴侣. ...
- c++ 的namespace及注意事项
前文 下文中的出现的"当前域"为"当前作用域"的简写 namepsace在c++中是用来避免不同模块下相同名字冲突的一种关键字,本文粗略的介绍了一下namesp ...
- VUE中 $on, $emit, v-on三者关系
VUE中 $on, $emit, v-on三者关系 每个vue实例都实现了事件借口 使用$on(eventName)监听事件 使用$emit(eventName)触发事件 若把vue看成家庭(相当于一 ...
- JS Foo.getName笔试题解析,杂谈静态属性与实例属性,变量提升,this指向,new一个函数的过程
壹 ❀ 引 Foo.getName算是一道比较老的面试题了,大致百度了一下在17年就有相关文章在介绍它,遗憾的是我在19年才遇到,比较奇妙的是现在仍有公司会使用这道题.相关解析网上是有的,这里我站在 ...
- 如何使redis中存放的都是热点数据?
当redis使用的内存超过设置的最大内存时,会触发redis的key淘汰机制,在redis3.0中的6中淘汰策略如下: (1)noeviction :不删除策略.当达到最大内存限制时,如果需要使用更多 ...
- PostgreSQL 安装 & 用户配置
一.为什么选择 PostgreSQL 自从MySQL被Oracle收购以后,PostgreSQL逐渐成为开源关系型数据库的首选. MySQL被oracle收购,innodb随之被oracle控制. 二 ...
- WebShell代码分析溯源(二)
WebShell代码分析溯源(二) 一.一句话变形马样本 <?php $POST['POST']='assert';$array[]=$POST;$array[0]['POST']($_POST ...