向作者致敬先附上git地址:https://github.com/forkingdog/UITableView-FDTemplateLayoutCell
写这篇文章的时候最新版为1.6(pod 'UITableView+FDTemplateLayoutCell', '~> 1.6')。
项目总共4个类文件,如下:
一:主类为UITableview+FDTemplateLayoutCell。看一下此类h文件所定义的东西
可以看出它就是通过分类的形式向UITableView中总共添加了5个方法。向UITableviewCell中添加了两个属性。
第一个方法fd_templateCellForReuseIdentifier: 这个方法用来获取模板cell,此cell职责就是用来计算高度。其实你在外部并不会用到这个方法,不知道作者为什么把这个方法暴露在外部(如果哪位看官知道何种情况下我们在外部需要用到此方法还请留言指教)。
第2,3,4个方法及时获取cell高度的方法,区别是第2个方法不使用高度缓存,第3个方法根据IndexPath缓存高度,第4个方法根据Key缓存高度,这个key是啥呢,就是你从服务器获取的获取的model列表,这个model中的可以唯一标示这个model的key。
第5个方法时用来获取UITableView的footer和header的高度的。这个方法是没有缓存高度的。
再来看看给UITableviewCell添加的两个属性。
fd_isTemplateLayoutCell这个属性为YES表示此cell是模板cell,模板cell只是用来计算的,不需要为它做UI方面的配置。你用第一个方法得到的cell就是模板cell。
fd_enforceFrameLayout这个属性默认为NO,为YES的话就是告诉第2,3,4个方法获取高度的时候不要使用-systemLayoutSizeFittingSize:方法,直接使用-sizeThatFits:方法。如果为NO则获取高度首选-systemLayoutSizeFittingSize:方法,如果-systemLayoutSizeFittingSize:方法获取的高度为0再使用-sizeThatFits:方法获取。
二:UITableView+FDIndexPathHeightCache和UITableView+FDKeyedHeightCache。这个两个类是缓存类,一个根据IndexPath缓存,一个根据Key缓存。
看一下这两个类的h文件定义了哪些东西:
首先都有5个方法,这5个方法的功能是一样的。
-existsHeightAtIndexPath:和-existsHeightForKey:是判断缓存是否已经存在。-CacheHeight:byIndexPath:和-cacheHeight:byKey:是保存高度缓存。-heightForIndexPath:和-heightForKey:是取缓存高度,如果缓存高度不存在,返回@(-1)。-invalidateHeightAtIndexPath:和-invalidateHeightForKey:用于删除单个特定缓存。-invalidateAllHeightCache用于删除全部缓存。
FDIndexPathHeightCache比FDKeyHeightCacha多了一个属性automaticallyInvalidateEnabled。这个属性的作用就是当你的UITableView调用-reloadDdata,-DeleteSections:withRowAnimation:,-deleteRowsAtIndexPaths:withRowAnimation:之类的操作时,就会自动删除对应的IndexPaths的高度缓存。虽然知道了这个属性的作用,但是查看保存缓存的方法-cacheHeightAtIndexPath:源码却发现是这样写的
这个方法里却把automaticallyInvalidateEnabled设为YES。那我们在外面再怎么设为NO,也会又被设为YES啊!不知道作者咋想的。
然后就是两个缓存类都通过分类的方式给UITableView添加了一个自己类型的缓存属性:fd_indexPathHeightCache和fd_keyHeightCache。
三:最后一个类就是UITableView+FDTemplateLayoutCellDebug。这个类就是用于打印debug信息的。这里就不多叙述了。
说完各个类的声明,接下来就进m文件看看具体实现。
一:UITableView+FDTemplateLayoutCell.m
就从-fd_heightForCellWithIdentifier: cacheByIndexPath: configuration:这个方法入手去看实现代码。如下:
我们忽略fd_debugLog方法不看。首先判断了一下UITableView持有的缓存类中(这个缓存类是懒加载的,首次调用创建)是否已经存在了对应IndexPath的高度缓存,如果以存在直接返回缓存中的高度。如果缓存中没有找到,那么就调用-fd_heightForCellWithIdentifier: configuration:去计算。然后将计算结果保存到缓存中,最后返回高度。我们先不管缓存类是怎么查找缓存和保存缓存的,之后我们会到缓存类中去研究。此处我们先去看看缓存中没有对应缓存的情况下,是怎么去计算高度的。进入方法-fd_heightForCellWithIdentifier: configuration:中:
首先根据identifier得到对应的模板cell:templateLayoutCell(有关模板cell的保存和创建很简单就不说了,大家自己一看代码便知)。得到templateCell之后先给用户一个机会去配置templateCell通过configuration(templateLayoutCell),配置什么呢,主要是给UILabel的text赋值啊之类的。好让接下来去计算具体高度。赋值好之后重点来啦,那就是[self fd_systemFittingHeightForConfigurateCell:templateLayoutCell]。这个就是具体怎么计算高度的方法了。进入这个方法(方法太长,三部分才能完整显示):
第一步,先把模板cell的宽度设为UITableView的宽度。
第二步,在使用-systemLayoutSizeFittingSize:方法前给cell.contentView加上一个宽度约束。如果是ios10.3以后的系统,还要给cell.contentView的上下左右到cell的边距设为0。最后调用-systemLayoutSizeFittingSize:方法获取高度,获取高度之后把之前加的约束全部删除。
第三步,如果第二步获取的高度为0的话,那么就执行-sizeThatFits:方法去获取高度,如果你想使用此方法获取正确高度,那你就需要在自定义cell中重写-sizeThatFits:方法了,具体怎么重写,你可以看demo。如果通过-sizeThatFits:获取的高度还是0呢!那么就返回系统默认的高度了,也就是44。至此,高度计算完成,返回高度。
二:UITableView+FDIndexPathHeightCache.m
以IndexPath缓存类为例来说,无非就是持有一个NSMutableArray<NSMutableArray<NSNumber *> *>类型的二维数组,然后将高度以NSNumber形式保存进数组。然后就是通过method_exchangeImplementations()截获UITableView的reloadData,DeleteSections:withRowAnimation:等方法,好在用户执行这些方法的时候判断是否删除对用IndexPath的高度缓存。如下图:
三:UITableView+FDKeyedHeightCache.m
key缓存就更简单了,使用NSMutableDictionary<id<NSCopying>, NSNumber *>类型的可变字典保存高度。
好了,关于UITableView+FDTemplateLayoutCell的源码解析就到这里,其实很简单。借此机会锻炼一下自己写此类文章。使用这个库最重要的部分还是在于自定义cell的时候,的布局和约束的添加,关于这部分大家可以到网上了解一下self-sizing cell这个概念。
最后欢迎大家阅后批评指导!