一、介绍

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. c#串口通信并处理接收的多个参数

    最近摸索做个上位机,简单记录一下关键的几个部分 c#做串口通信主要使用的是System.IO.Ports类,其实还是十分方便的 最终效果如下: 千万不要忘记了下面这个 填写串口相关配置 我们可以通过G ...

  2. 华为2019年NE40E-X8,承诺命令

    commit每敲一行命令,都得确认一下.以防误操作.

  3. 以太网驱动的流程浅析(四)-以太网驱动probe流程【原创】

    以太网驱动的流程浅析(四)-以太网驱动probe流程 Author:张昺华 Email:920052390@qq.com Time:2019年3月23日星期六 此文也在我的个人公众号以及<Lin ...

  4. RabbitMQ基础理解

    RabbitMQ基本理解 MQ是消息中间件,常见的有RabbitMQ,Kafka,RocketMQ,activeMQ 等,用于分布式系统中.作用有三点 解耦 异步 削峰 RabbitMQ 整体上是一个 ...

  5. Redis缓存与数据库一致性解决方案

    背景 缓存是数据库的副本,应用在查询数据时,先从缓存中查询,如果命中直接返回,如果未命中,去数据库查询最新数据并返回,同时写入缓存. 缓存能够有效地加速应用的读写速度,同时也可以降低后端负载.是应用架 ...

  6. pandas.apply()函数

    1.介绍 apply函数是pandas里面所有函数中自由度最高的函数.该函数如下: DataFrame.apply(func, axis=0, broadcast=False, raw=False, ...

  7. cookie及其特点

    关于cookie我们首先要知道cookie是指会话跟踪技术 我们可以用它来做一下事情,但是我们需要清楚cookie是不安全的 功能: 会话状态管理(如用户登录状态.购物车.游戏分数和其它需要记录的信息 ...

  8. 【解决】MySQL提示启动成功,实际进程并没有起来

    一.概括: 1.查看运行日志 vim /var/log/mariadb/mariadb.log 2.修改配置文件 vim /etc/my.cnf 3.修改文件权限 chown mysql.mysql ...

  9. idea中配置maven的骨架本地下载方式

    由于我们使用maven的骨架创建的时候,maven需要联网进行骨架的下载,如果断网了,则骨架不能正常下载,为了防止这种情况,我们可以配置本地下载,当已经联网下载过一次后,以后每次进行下载都会从本地下载 ...

  10. C# loop executed one by one wait the former completed

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...