使用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 简介 有人称它为“钩子”,有人称它为“挂钩”技术.谈到钩子,很容易让人联想到在钓东西,比如鱼钩就用于钓鱼.编程技术的钩子也是在等待捕获系统中的某个消息或者动作.钩子的应用范围非常广泛,比如输 ...
随机推荐
- Python的map方法的应用
Map方法,第一个参数要写一个匿名函数表达式,或者是一个函数引用,第二个第三个往后都是表达式用到的参数,参数一般是可迭代的 1.比如下面这个map方法,两个参数,第一个 lambda x: x*x是匿 ...
- java 主动信任证书
java 主动信任证书 SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mid.toCharArray() ...
- 【安富莱】RTX嵌入式操作系统教程发布,支持F103,F407和F429,含81个配套例程(2017-10-17)
前言说明:1. 首先感谢大家对我们安富莱电子一年来的支持,2016年我们会再接再厉推出更好的教程. 2. 估计也有网友会问RTX的优势在那里,针对这个问题,教程中第一章分为6条专门回答了这个问题,有兴 ...
- PHP 将内容写入word pdf 换行符不生效咋办
答:把单引号换成双引号就能解析换行符(\n)了,
- Ubuntu下预览raw格式图片
默认Ubuntu下资源管理器是不可以直接预览raw格式图片的,这就给查看图片带来很大的不便,下面我们就选择安装UFRaw来预览Raw格式图片 1. 首先在terminal安装UFRaw sudo ap ...
- Astyle 一键格式化项目代码
代码格式化差异问题: 一个团队有多个开发,因开发习惯不同,开发时少添加了空格.换行等. 格式化代码时,一般会将整个文档格式化,代码提交时会发现未知的修改项. Astyle格式化工具 官网下载地址:Ar ...
- 订单结算submit_order.php扣库存,扣账号金额(学生笔记)
<?php header("Content-type: text/html; charset=utf-8"); session_start(); include_once(& ...
- JS-字符串截取方法slice、substring、substr的区别
一.使用 slice() 截取 1,函数说明 slice() 方法可通过指定的开始和结束位置,提取字符串的某个部分,并以新的字符串返回被提取的部分.语法如下: stringObject.slice(s ...
- 利用Dynamics 365 Customer Engagement的标准导入功能导入附件。
我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...
- CSS学习笔记_1
1.调用 内部样式表: css可以直接在html代码里面使用</style></style>节点.允许html代码中有两个style节点,但是起作用的是靠后的节点 如图的代码, ...