向作者致敬先附上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職責(zé)就是用來計算高度。其實你在外部并不會用到這個方法,不知道作者為什么把這個方法暴露在外部(如果哪位看官知道何種情況下我們在外部需要用到此方法還請留言指教)。
第2,3,4個方法及時獲取cell高度的方法,區(qū)別是第2個方法不使用高度緩存,第3個方法根據(jù)IndexPath緩存高度,第4個方法根據(jù)Key緩存高度,這個key是啥呢,就是你從服務(wù)器獲取的獲取的model列表,這個model中的可以唯一標(biāo)示這個model的key。
第5個方法時用來獲取UITableView的footer和header的高度的。這個方法是沒有緩存高度的。
再來看看給UITableviewCell添加的兩個屬性。
fd_isTemplateLayoutCell這個屬性為YES表示此cell是模板cell,模板cell只是用來計算的,不需要為它做UI方面的配置。你用第一個方法得到的cell就是模板cell。
fd_enforceFrameLayout這個屬性默認(rèn)為NO,為YES的話就是告訴第2,3,4個方法獲取高度的時候不要使用-systemLayoutSizeFittingSize:方法,直接使用-sizeThatFits:方法。如果為NO則獲取高度首選-systemLayoutSizeFittingSize:方法,如果-systemLayoutSizeFittingSize:方法獲取的高度為0再使用-sizeThatFits:方法獲取。
二:UITableView+FDIndexPathHeightCache和UITableView+FDKeyedHeightCache。這個兩個類是緩存類,一個根據(jù)IndexPath緩存,一個根據(jù)Key緩存。
看一下這兩個類的h文件定義了哪些東西:
首先都有5個方法,這5個方法的功能是一樣的。
-existsHeightAtIndexPath:和-existsHeightForKey:是判斷緩存是否已經(jīng)存在。-CacheHeight:byIndexPath:和-cacheHeight:byKey:是保存高度緩存。-heightForIndexPath:和-heightForKey:是取緩存高度,如果緩存高度不存在,返回@(-1)。-invalidateHeightAtIndexPath:和-invalidateHeightForKey:用于刪除單個特定緩存。-invalidateAllHeightCache用于刪除全部緩存。
FDIndexPathHeightCache比FDKeyHeightCacha多了一個屬性automaticallyInvalidateEnabled。這個屬性的作用就是當(dāng)你的UITableView調(diào)用-reloadDdata,-DeleteSections:withRowAnimation:,-deleteRowsAtIndexPaths:withRowAnimation:之類的操作時,就會自動刪除對應(yīng)的IndexPaths的高度緩存。雖然知道了這個屬性的作用,但是查看保存緩存的方法-cacheHeightAtIndexPath:源碼卻發(fā)現(xiàn)是這樣寫的
這個方法里卻把automaticallyInvalidateEnabled設(shè)為YES。那我們在外面再怎么設(shè)為NO,也會又被設(shè)為YES啊!不知道作者咋想的。
然后就是兩個緩存類都通過分類的方式給UITableView添加了一個自己類型的緩存屬性:fd_indexPathHeightCache和fd_keyHeightCache。
三:最后一個類就是UITableView+FDTemplateLayoutCellDebug。這個類就是用于打印debug信息的。這里就不多敘述了。
說完各個類的聲明,接下來就進m文件看看具體實現(xiàn)。
一:UITableView+FDTemplateLayoutCell.m
就從-fd_heightForCellWithIdentifier: cacheByIndexPath: configuration:這個方法入手去看實現(xiàn)代碼。如下:
我們忽略fd_debugLog方法不看。首先判斷了一下UITableView持有的緩存類中(這個緩存類是懶加載的,首次調(diào)用創(chuàng)建)是否已經(jīng)存在了對應(yīng)IndexPath的高度緩存,如果以存在直接返回緩存中的高度。如果緩存中沒有找到,那么就調(diào)用-fd_heightForCellWithIdentifier: configuration:去計算。然后將計算結(jié)果保存到緩存中,最后返回高度。我們先不管緩存類是怎么查找緩存和保存緩存的,之后我們會到緩存類中去研究。此處我們先去看看緩存中沒有對應(yīng)緩存的情況下,是怎么去計算高度的。進入方法-fd_heightForCellWithIdentifier: configuration:中:
首先根據(jù)identifier得到對應(yīng)的模板cell:templateLayoutCell(有關(guān)模板cell的保存和創(chuàng)建很簡單就不說了,大家自己一看代碼便知)。得到templateCell之后先給用戶一個機會去配置templateCell通過configuration(templateLayoutCell),配置什么呢,主要是給UILabel的text賦值啊之類的。好讓接下來去計算具體高度。賦值好之后重點來啦,那就是[self fd_systemFittingHeightForConfigurateCell:templateLayoutCell]。這個就是具體怎么計算高度的方法了。進入這個方法(方法太長,三部分才能完整顯示):
第一步,先把模板cell的寬度設(shè)為UITableView的寬度。
第二步,在使用-systemLayoutSizeFittingSize:方法前給cell.contentView加上一個寬度約束。如果是ios10.3以后的系統(tǒng),還要給cell.contentView的上下左右到cell的邊距設(shè)為0。最后調(diào)用-systemLayoutSizeFittingSize:方法獲取高度,獲取高度之后把之前加的約束全部刪除。
第三步,如果第二步獲取的高度為0的話,那么就執(zhí)行-sizeThatFits:方法去獲取高度,如果你想使用此方法獲取正確高度,那你就需要在自定義cell中重寫-sizeThatFits:方法了,具體怎么重寫,你可以看demo。如果通過-sizeThatFits:獲取的高度還是0呢!那么就返回系統(tǒng)默認(rèn)的高度了,也就是44。至此,高度計算完成,返回高度。
二:UITableView+FDIndexPathHeightCache.m
以IndexPath緩存類為例來說,無非就是持有一個NSMutableArray<NSMutableArray<NSNumber *> *>類型的二維數(shù)組,然后將高度以NSNumber形式保存進數(shù)組。然后就是通過method_exchangeImplementations()截獲UITableView的reloadData,DeleteSections:withRowAnimation:等方法,好在用戶執(zhí)行這些方法的時候判斷是否刪除對用IndexPath的高度緩存。如下圖:
三:UITableView+FDKeyedHeightCache.m
key緩存就更簡單了,使用NSMutableDictionary<id<NSCopying>, NSNumber *>類型的可變字典保存高度。
好了,關(guān)于UITableView+FDTemplateLayoutCell的源碼解析就到這里,其實很簡單。借此機會鍛煉一下自己寫此類文章。使用這個庫最重要的部分還是在于自定義cell的時候,的布局和約束的添加,關(guān)于這部分大家可以到網(wǎng)上了解一下self-sizing cell這個概念。
最后歡迎大家閱后批評指導(dǎo)!