RumTime实践之--UITableView和UICollectionView缺省页的实现
有关RunTime的知识点已经看过很久了,但是一直苦于在项目中没有好的机会进行实际运用,俗话说“光说不练假把式”,正好最近在项目中碰到一个UITableView和UICollectionView在数据缺省的情况下展示默认缺省页的需求,这个时候RunTime大展拳脚的时候就到了。
大致的实现思路是这样的,因为UITableView和UICollectionView都是继承自系统的UIScrollView,所以为了同时实现UITableView和UICollectionView的缺省页,我们可以通过对UIScrollView进行扩展来达到这一目标。UITableView和UICollectionView都可以通过dataSource设置它们的数据源,而我们需要做的就是在UITableView和UICollectionView设置数据源之后去获取它们的数据源,从而得知当前UITableView和UICollectionView是否处于缺省状态。当UITableView和UICollectionView处于缺省状态时,我们就将加载缺省页视图。
#import <UIKit/UIKit.h> @protocol EmptyDataViewDataSource; @interface UIScrollView (EmptyData) @property (nonatomic, weak) id <EmptyDataViewDataSource> emptyViewDataSource; @end @protocol EmptyDataViewDataSource <NSObject> - (UIView *)emptyDataCustomView; @end
首先实现一个UIScrollView的Category,在这个Category中声明一个EmptyDataViewDataSource的代理。当然,由于Category是在编译期间进行决议的,而OC的类对象在内存中是以结构体的形式排布的,结构体的大小是不能动态改变的,因此Category是不能动态地添加成员变量的,但是我们可以通过runtime中的
objc_setAssociatedObject和objc_getAssociatedObject方法去创建一个关联对象,从而在外部看起来好像我们为一个Category添加了一个成员变量。
我们自定义EmptyDataViewDataSource的set和get方法。创建对象ProtocolContainer,并将它关联到self上。之后执行SelectorShouldBeSwizzle方法,对系统方法进行替换。
- (void)setEmptyViewDataSource:(id<EmptyDataViewDataSource>)emptyViewDataSource
{
ProtocolContainer *container = [[ProtocolContainer alloc] initWithProtocol:emptyViewDataSource];
objc_setAssociatedObject(self, kEmptyDataDataSource, container, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self SelectorShouldBeSwizzle]; //在EmptyViewDataSource的set方法中实现系统方法的替换操作
}
- (id<EmptyDataViewDataSource>)emptyViewDataSource
{
ProtocolContainer *container = objc_getAssociatedObject(self, kEmptyDataDataSource);
return container.protocolContainer;
}
EmptyDataViewDataSource set&get方法
在SelectorShouldBeSwizzle方法中不论是UITableView还是UICollectionView我们都需要替换系统的reloadData方法。由于UITableView还有插入,删除等操作,在这些操作后系统会自动去调用UITableView中的endUpdates方法,因此我们还需要替换endUpdates方法。
/**
需要被替换的系统方法
*/
- (void)SelectorShouldBeSwizzle
{
[self methodSwizzle:@selector(reloadData)]; //替换系统的reloadData实现方法 if ([self isKindOfClass:[UITableView class]]) //由于tableView有Cell的插入,删除实现,需要替换tableView的endUpdates方法,在操作结束后判断是否为空
{
[self methodSwizzle:@selector(endUpdates)];
}
}
方法替换的实现主要依赖于runtime中的class_getInstanceMethod和method_setImplementation方法。我们先来看看class_getInstanceMethod方法的描述
/**
* Returns a specified instance method for a given class.
*
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* \e name for the class specified by \e cls, or \c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
从中我们可以看到这个方法的返回值是一个Method,那么什么是Method呢?Method是一个指向objc_method类型的结构体的指针,这个结构体中有三个成员变量,SEL类型的method_name(我的理解为方法的名称),char*类型的method_types(方法的参数)以及IMP类型的method_imp(方法的实现)。SEL和IMP是一种映射关系,OC在运行时通过SEL方法名去找方法对应的IMP实现,这也是为什么OC不能像C++那样实现函数重载的原因,因为在OC中方法名和实现是一一对应的关系,重名了OC就没办法去找对应的实现方法了(貌似有点扯远了。。。)现在我们就可以很好的理解class_getInstanceMethod这个方法了,这个方法实现的就是去Class cls中去找一个叫做SEL叫做name的方法, 并把这个方法返回。
/**
* Sets the implementation of a method.
*
* @param m The method for which to set an implementation.
* @param imp The implemention to set to this method.
*
* @return The previous implementation of the method.
*/
OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
现在再来看method_setImplementation方法就很好理解了。使用我们传入的IMP imp去替换Method中原来的IMP实现,并返回原来的IMP实现。在理解完了这两个方法以后我们再回到代码上来。
/**
替换系统方法的实现 @param selector 需要被替换的系统方法
*/
- (void)methodSwizzle:(SEL)selector
{
NSAssert([self respondsToSelector:selector], @"self不能响应selector方法"); if (!_cacheDictionary) //如果缓存字典为空,开辟空间
{
_cacheDictionary = [[NSMutableDictionary alloc] init];
} NSString *selfClass = NSStringFromClass([self class]); //获取当前类的类型
NSString *selectorName = NSStringFromSelector(selector); //获取方法名
NSString *key = [NSString stringWithFormat:@"%@_%@",selfClass,selectorName]; //类型+方法名组成需要被替换的方法的key NSValue *value = [_cacheDictionary objectForKey:key]; //查询方法是否被替换 if (value)
{
return; //方法被替换时,直接return
} Method method = class_getInstanceMethod([self class], selector); IMP originalImplemention = method_setImplementation(method, (IMP)newImplemention); //获取替换前的系统方法实现 [_cacheDictionary setObject:[NSValue valueWithPointer:originalImplemention] forKey:key]; //缓存替换前的系统方法实现
}
methodSwizzle
我们为乐不每一次都去执行方法的替换操作,我们使用一个缓存字典来存储已经被替换过的方法。我们使用类的类型和被替换掉实现的方法名组合起来作为缓存字典中存储的key值,value当然就是被替换下来的系统方法的实现。接下来我们看看我们在替换上去的newImplemention实现中做了什么。
/**
被替换后的方法 @param self self
@param _cmd 方法名称
*/
void newImplemention(id self, SEL _cmd)
{
NSString *selfClass = NSStringFromClass([self class]);
NSString *selectorName = NSStringFromSelector(_cmd);
NSString *key = [NSString stringWithFormat:@"%@_%@",selfClass,selectorName]; //使用当前的类名和当前方法的名称组合称为key值 NSValue *value = [_cacheDictionary objectForKey:key]; //通过key从缓存字典中取出系统原来方法实现 IMP originalImplemention = [value pointerValue]; if (originalImplemention)
{
((void(*)(id,SEL))originalImplemention)(self,_cmd); //执行被替换前系统原来的方法
} if([self canEmptyDataViewShow]) //判断是否需要展示缺省页
{
[self reloadEmptyView];
}
}
newImplemention(id self, SEL _cmd)
在OC中,每个方法被调用时,系统都会自动的为方法添加两个参数,一个self,另一个就是被调用的方法的名称,也就是我们的newImplemention中的两个入参。我们取出来系统原来的实现方法,这一步是必须的,毕竟我们的本意并不是抹除掉系统方法,而是在调用系统方法执行后判断是否需要展示缺省页而已啦。。。因此通过调用IMP的方式,直接调用系统方法实现。之后判断是否需要展示缺省页。
- (BOOL)canEmptyDataViewShow
{
NSInteger itemCount = ; NSAssert([self respondsToSelector:@selector(dataSource)], @"tableView或CollectionView没有实现dataSource"); if ([self isKindOfClass:[UITableView class]])
{
UITableView *tableView = (UITableView *)self;
id<UITableViewDataSource> dataSource = tableView.dataSource;
NSInteger sections = ;
if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)])
{
sections = [dataSource numberOfSectionsInTableView:tableView];
}
if(dataSource && [dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)])
{
for (NSInteger i=; i<sections; i++) {
itemCount += [dataSource tableView:tableView numberOfRowsInSection:i];
}
}
} if ([self isKindOfClass:[UICollectionView class]])
{
UICollectionView *collectionView = (UICollectionView *)self;
id<UICollectionViewDataSource> dataSource = collectionView.dataSource;
NSInteger sections = ;
if (!dataSource || [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
{
sections = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
}
if (!dataSource || [dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)])
{
for (NSInteger i=; i<sections; i++) {
itemCount += [dataSource collectionView:collectionView numberOfItemsInSection:i];
}
}
} if (itemCount == )
{
return YES;
}
else
{
return NO;
}
}
- (BOOL)canEmptyDataViewShow
我们获取到对象本身,并从对象中取出它的数据源,然后判断数据源的item是否为0 ,当为0时,就可以自动展示缺省页了。
- (void)reloadEmptyView
{
UIView *emptyView;
if (self.emptyViewDataSource && [self.emptyViewDataSource respondsToSelector:@selector(emptyDataCustomView)])
{
emptyView = [self.emptyViewDataSource emptyDataCustomView];
}
else
{
emptyView = self.emptyDataView;
emptyView.backgroundColor = [UIColor redColor];
} if (!emptyView.superview)
{
if (self.subviews.count > )
{
[self insertSubview:emptyView atIndex:];
}
}
emptyView.frame = self.frame;
}
- (void)reloadEmptyView
还记得我们在之前设置的EmptyDataViewDataSource吗?既然已经设置了EmptyDataViewDataSource,那我们就要把它充分利用起来嘛,来个自定义的emptyView也不是什么问题嘛~
源码github地址:https://github.com/cgy-tiaopi/CGYEmptyData
RumTime实践之--UITableView和UICollectionView缺省页的实现的更多相关文章
- UITableView和UICollectionView的方法学习一
参考资料 UITableView UICollectionView UICollectionViewDataSource UICollectionViewDelegate UICollectionVi ...
- UItableView嵌套UICollectionView
首先我们需要继承一下UITableView并且遵守<UITableViewDelegate,UITableViewDataSource,UICollectionViewDataSource,UI ...
- UITableView和UICollectionView的Cell高度的几种设置方式
UITableViewCell 1.UITableView的Cell高度默认由rowHeight属性指定一个低优先级的隐式约束 2.XIB中可向UITableViewCell的contentView添 ...
- iOS 8自动调整UITableView和UICollectionView布局
本文转载自:http://tech.techweb.com.cn/thread-635784-1-1.html 本文讲述了UITableView.UICollectionView实现 self-siz ...
- iOS中UITableView和UICollectionView的默认空态页
项目中想实现空态页风格统一控制的效果,就封装了一个默认空态页,使用的技术点有:1 方法替换 ,2 给分类(Category)添加属性. 我们知道,扩展(extension)可以给类添加私有变量和方法. ...
- UITableVIew与UICollectionView带动画删除cell时崩溃的处理
UITableVIew与UICollectionView带动画删除cell时崩溃的处理 -会崩溃的原因是因为没有处理好数据源与cell之间的协调关系- 效果: tableView的源码: ModelC ...
- [转]iOS8 自动调整UITableView和UICollectionView布局
转自:http://www.cocoachina.com/industry/20140825/9450.html (via:玉令天下的Blog) 本文讲述了UITableView.UICollec ...
- 封装Button ,封装UITableView,封装UICollectionView
---恢复内容开始--- 封装Button ,封装UITableView,封装UICollectionView: 1.实现Button的创建和点击事件不用分开操作处理; 2.实现UITableView ...
- iOS全埋点解决方案-UITableView和UICollectionView点击事件
前言 在 $AppClick 事件采集中,还有两个比较特殊的控件: UITableView •UICollectionView 这两个控件的点击事件,一般指的是点击 UITableViewCell 和 ...
随机推荐
- 【01-05】hibernate BaseDao
BaseDao接口定义 package org.alohaworld.util.dao; import java.io.Serializable; import java.util.List; imp ...
- U盘容量变小解决办法
之前买了个三星闪盘,容量32G,USB3.0 后来装了U盘系统Kali Linux,最近想用的时候发现容量变为6GB了,真的很奇怪. 于是万能的百度(别说为什么不用谷歌,防火墙呀...) 找到解决办法 ...
- 一个ListView怎么展示两种样式
private class MyBaseMsgAdapter extends BaseAdapter { //获取数据适配器中条目类型的总数,修改成两种(纯文本,输入+文字) @Override pu ...
- iOS-关于使用其他应用打开本应用文档
简介:本片文章是对官方文档的翻译,非常的感谢文章的翻译者:颐和园 官方地址:Document Interaction Programming Topics for iOS 文章的介绍内容: ***** ...
- 模板化的七种排序算法,适用于T* vector<T>以及list<T>
最近在写一些数据结构以及算法相关的代码,比如常用排序算法以及具有启发能力的智能算法.为了能够让写下的代码下次还能够被复用,直接将代码编写成类模板成员函数的方式,之所以没有将这种方式改成更方便的函数模板 ...
- webpack 配置文件
现如今,webpack非常的受欢迎,比较火的几款js框架都推荐使用webpack来构建项目,而webpack也确实非常强大,但是配置webpack缺常常带来很多问题,接下来就写一下我自己遇到的一些坑. ...
- 微信共享收货地址 edit_address:fail 吐白沫级解决方案
又被微信坑了一整天,看完官方文档怎么测试都不通过,我一直怀疑是新版本微信支付我没有设置“共享收货地址”开关造成的. 后来经过验证,新版本不需要做这件事了. 那么,我没错,是微信的文档没及时更新... ...
- docker笔记
安装...不说了 docker info 查看信息 docker pull ...拉取镜像 docker run -it [镜像名] 运行 docker ps查看当前运行的容器 docker ps ...
- java dom4j封装和解析XML
package org.scbit.lsbi.scp.utils; import java.util.ArrayList; import java.util.List; import org.dom4 ...
- yii小细节
1.main.php增加导航栏严格区分大小写,否则会出现404错误 2.增加'分页'功能---前后台的models里面的search.php 添加 public function search($pa ...