UITableView+FDTemplateLayoutCell 源碼探究
- 在我們?nèi)粘5臉I(yè)務(wù)中,常常伴隨大量的UITableView,然而動態(tài)地計算Cell的高度常常困擾著我稿湿。自從使用了這個組件之后,一切都變得沒那么復(fù)雜。所以深入學(xué)習(xí)下這個框架的組件的實現(xiàn)原理窍仰。
- 框架地址:https://github.com/forkingdog/UITableView-FDTemplateLayoutCell
代碼文件目錄
- UITableView+FDIndexPathHeightCache.h
- UITableView+FDIndexPathHeightCache.m
- UITableView+FDKeyedHeightCache.h
- UITableView+FDKeyedHeightCache.m
- UITableView+FDTemplateLayoutCell.h
- UITableView+FDTemplateLayoutCell.m
- UITableView+FDTemplateLayoutCellDebug.h
- UITableView+FDTemplateLayoutCellDebug.m
首先,介紹一下這幾個類的基本功能礼殊,再層層推進驹吮,逐一分析。
- UITableView+FDIndexPathHeightCache晶伦,主要負責(zé)cell通過NSIndexPath進行緩存高度的功能
- UITableView+FDKeyedHeightCache碟狞,主要負責(zé)cell通過key值進行緩存高度的功能
- UITableView+FDTemplateLayoutCell,提供接口方法方便用戶定義cell的數(shù)據(jù)源婚陪,以及幫助我們計算cell的高度
- UITableView+FDTemplateLayoutCellDebug族沃,提供一些Debug打印信息
關(guān)于這個框架,坦白說,從代碼中看脆淹,作者無疑秀了一波runtime底層的功底常空,讓我這種小白起初一臉懵逼。自然我得換種思路來解讀這個框架盖溺,那就是從字數(shù)最少的類入手吧漓糙。
UITableView+FDTemplateLayoutCellDebug
@interface UITableView (FDTemplateLayoutCellDebug)
//設(shè)置Debug模式是否打開
@property (nonatomic, assign) BOOL fd_debugLogEnabled;
//通過該方法,傳遞NSLog打印對應(yīng)的Debug信息
- (void)fd_debugLog:(NSString *)message;
@end
@implementation UITableView (FDTemplateLayoutCellDebug)
- (BOOL)fd_debugLogEnabled {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setFd_debugLogEnabled:(BOOL)debugLogEnabled {
objc_setAssociatedObject(self, @selector(fd_debugLogEnabled), @(debugLogEnabled), OBJC_ASSOCIATION_RETAIN);
}
- (void)fd_debugLog:(NSString *)message {
if (self.fd_debugLogEnabled) {
NSLog(@"** FDTemplateLayoutCell ** %@", message);
}
}
@end
- 在分類中烘嘱,如果要聲明屬性昆禽,可以通過使用關(guān)聯(lián)度對象( AssociatedObject ), 通過objc_setAssociatedObject() 添加屬性,objc_getAssociatedObject() 獲取屬性蝇庭。實際上醉鳖,相當于在運行時系統(tǒng)中動態(tài)地在內(nèi)存中開辟一塊空間,存儲debugLogEnabled這個BOOL變量遗契,類似懶加載的方式辐棒,通過runtime實現(xiàn)setter & getter方法。
- 關(guān)于runtime的知識點牍蜂,推薦這篇博客:http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
UITableView+FDKeyedHeightCache
#import <UIKit/UIKit.h>
@interface FDKeyedHeightCache : NSObject
//判斷緩存中是否存在key為值的緩存高度
- (BOOL)existsHeightForKey:(id<NSCopying>)key;
//對指定key的cell設(shè)置高度為height
- (void)cacheHeight:(CGFloat)height byKey:(id<NSCopying>)key;
//從緩存中獲取對應(yīng)key的cell的高度height值
- (CGFloat)heightForKey:(id<NSCopying>)key;
//從緩存中刪除指定key的cell的值
- (void)invalidateHeightForKey:(id<NSCopying>)key;
//移除緩存中所有的cell的高度緩存值
- (void)invalidateAllHeightCache;
@end
@interface UITableView (FDKeyedHeightCache)
@property (nonatomic, strong, readonly) FDKeyedHeightCache *fd_keyedHeightCache;
@end
- 先來看看FDKeyedHeightCache類中聲明的屬性
@property (nonatomic, strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableHeightsByKeyForPortrait;
@property (nonatomic, strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableHeightsByKeyForLandscape;
不難看出漾根,這是兩個指定泛型的可變字典。
- mutableHeightsByKeyForPortrait : 用于緩存設(shè)備豎直放置時鲫竞,對應(yīng)key的cell的高度值辐怕。
- mutableHeightsByKeyForLandscape : 用于緩存設(shè)備橫向放置時,對應(yīng)key的cell的高度值从绘。
- FDKeyedHeightCache中的接口方法
- (BOOL)existsHeightForKey:(id<NSCopying>)key {
NSNumber *number = self.mutableHeightsByKeyForCurrentOrientation[key];
return number && ![number isEqualToNumber:@-1];
}
- (void)cacheHeight:(CGFloat)height byKey:(id<NSCopying>)key {
self.mutableHeightsByKeyForCurrentOrientation[key] = @(height);
}
- (CGFloat)heightForKey:(id<NSCopying>)key {
#if CGFLOAT_IS_DOUBLE
return [self.mutableHeightsByKeyForCurrentOrientation[key] doubleValue];
#else
return [self.mutableHeightsByKeyForCurrentOrientation[key] floatValue];
#endif
}
- (void)invalidateHeightForKey:(id<NSCopying>)key {
[self.mutableHeightsByKeyForPortrait removeObjectForKey:key];
[self.mutableHeightsByKeyForLandscape removeObjectForKey:key];
}
- (void)invalidateAllHeightCache {
[self.mutableHeightsByKeyForPortrait removeAllObjects];
[self.mutableHeightsByKeyForLandscape removeAllObjects];
}
- 這些方法并不晦澀寄疏,看到這里,大家不禁會問僵井,self.mutableHeightsByKeyForCurrentOrientation從何而來陕截,這也是我覺得這個類中,細節(jié)處理比較好的地方批什,由于此處考慮到緩存的高度區(qū)別了設(shè)備方向农曲,所以框架作者,通過一個getter方法來獲取對應(yīng)的存放高度的字典驻债。
- (NSMutableDictionary<id<NSCopying>, NSNumber *> *)mutableHeightsByKeyForCurrentOrientation {
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.mutableHeightsByKeyForPortrait: self.mutableHeightsByKeyForLandscape;
}
-
根據(jù)UIDeviceOrientationIsPortrait()函數(shù)乳规,傳入當前設(shè)備的放置方向([UIDevice currentDevice].orientation
)進行判斷。從而便可以通過屬性簡潔判斷需要從那個字典中取值了合呐。
UITableView+FDIndexPathHeightCache
@interface FDIndexPathHeightCache : NSObject
// 如果您使用索引路徑獲取高度緩存暮的,則自動啟用
@property (nonatomic, assign) BOOL automaticallyInvalidateEnabled;
// Height cache
- (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath;
- (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath;
- (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath;
- (void)invalidateAllHeightCache;
@end
@interface UITableView (FDIndexPathHeightCache)
@property (nonatomic, strong, readonly) FDIndexPathHeightCache *fd_indexPathHeightCache;
@end
@interface UITableView (FDIndexPathHeightCacheInvalidation)
/// 當你不想通過刪除緩存中的高度來刷新數(shù)據(jù)源重新計算時,可以調(diào)用這個方法淌实。
/// 該方法中用過runtime重寫了tableView中修改cell的一些方法冻辩,例如插入cell猖腕,刪除cell,移動cell微猖,以及reloadData方法谈息。
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache;
@end
- 首先看看FDIndexPathHeightCache中設(shè)置的屬性
typedef NSMutableArray<NSMutableArray<NSNumber *> *> FDIndexPathHeightsBySection;
@interface FDIndexPathHeightCache ()
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForPortrait;
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForLandscape;
@end
通過前面key的高度緩存分析,不難猜出這幾個屬性是干什么的凛剥。
- 由于通過NSIndexPath獲取高度緩存侠仇,NSIndexPath對應(yīng)section, 以及indexPath。FDIndexPathHeightsBySection這個數(shù)組犁珠,通過數(shù)組嵌套字典的數(shù)據(jù)結(jié)構(gòu)來存儲逻炊,不同的section組中對應(yīng)的cell的高度緩存。
-
FDIndexPathHeightCache中的方法
由于頭文件聲明的幾個接口方法犁享,與FDKeyedHeightCache中的思路類似余素,就不再費口舌了,大家翻看源碼便一目了然炊昆。
- (void)enumerateAllOrientationsUsingBlock:(void (^)(FDIndexPathHeightsBySection *heightsBySection))block {
block(self.heightsBySectionForPortrait);
block(self.heightsBySectionForLandscape);
}
- (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath {
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection {
heightsBySection[indexPath.section][indexPath.row] = @-1;
}];
}
- (void)invalidateAllHeightCache {
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
[heightsBySection removeAllObjects];
}];
}
- (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths {
// Build every section array or row array which is smaller than given index path.
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[self buildSectionsIfNeeded:indexPath.section];
[self buildRowsIfNeeded:indexPath.row inExistSection:indexPath.section];
}];
}
- (void)buildSectionsIfNeeded:(NSInteger)targetSection {
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
for (NSInteger section = 0; section <= targetSection; ++section) {
if (section >= heightsBySection.count) {
heightsBySection[section] = [NSMutableArray array];
}
}
}];
}
- (void)buildRowsIfNeeded:(NSInteger)targetRow inExistSection:(NSInteger)section {
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
NSMutableArray<NSNumber *> *heightsByRow = heightsBySection[section];
for (NSInteger row = 0; row <= targetRow; ++row) {
if (row >= heightsByRow.count) {
heightsByRow[row] = @-1;
}
}
}];
}
- 這幾個封裝的方法桨吊,主要一點就是通過block來回調(diào),判斷刪除NSIndexPath對應(yīng)的cell高度緩存凤巨。
- 在這個類中视乐,最核心的莫過于UITableView (FDIndexPathHeightCacheInvalidation) 這個分類的實現(xiàn)細節(jié),廢話少說敢茁,繼續(xù)看代碼佑淀。
//我們只是轉(zhuǎn)發(fā)主調(diào)用,在崩潰報告中彰檬,最頂層的方法在堆椛烊校可能存在FD,
//但它真的不是我們的錯誤逢倍,你應(yīng)該檢查你的表視圖的數(shù)據(jù)源和
//重新加載時顯示單元格不匹配捧颅。
static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void (^callout)(void)) {
callout();
}
#define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0)
- 調(diào)用的接口方法
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache {
FDPrimaryCall([self fd_reloadData];);
}
- 這個方法,主要調(diào)用的是[self fd_reloadData]较雕,看到這里的時候隘道,我們的第一反應(yīng)應(yīng)該是此處通過runtime 交換了系統(tǒng)方法的實現(xiàn)。這是一種動態(tài)的攔截技巧郎笆,也算是基礎(chǔ)的runtime知識了,懵逼的小伙伴可以認真閱讀下前面提到的關(guān)于runtime的大牛博文忘晤。
- 既然如此宛蚓,先來看看作者重寫了哪些系統(tǒng)的方法吧。
+ (void)load {
// All methods that trigger height cache's invalidation
SEL selectors[] = {
@selector(reloadData),
@selector(insertSections:withRowAnimation:),
@selector(deleteSections:withRowAnimation:),
@selector(reloadSections:withRowAnimation:),
@selector(moveSection:toSection:),
@selector(insertRowsAtIndexPaths:withRowAnimation:),
@selector(deleteRowsAtIndexPaths:withRowAnimation:),
@selector(reloadRowsAtIndexPaths:withRowAnimation:),
@selector(moveRowAtIndexPath:toIndexPath:)
};
for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
SEL originalSelector = selectors[index];
SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- 通過method_exchangeImplementations() C函數(shù)设塔, 將重寫的方法凄吏,一一交換成重寫的方法。
- 在這些fd_方法中的實現(xiàn)細節(jié)中,需要注意的一點就是痕钢,如果對應(yīng)的fd_indexPathHeightCache設(shè)置了automaticallyInvalidateEnabled屬性為YES時图柏,對應(yīng)的方法對高度緩存做相應(yīng)的處理,重新更新fd_indexPathHeightCache中存儲的高度緩存任连。
- 當?shù)谝淮蝦eloadData蚤吹,或者cell的行數(shù)發(fā)生變化(增減行,section) 随抠,會先在tableview不處于滾動狀態(tài)的時候異步計算那些沒有被計算過的cell的高度裁着,做預(yù)緩存,這個想法非常贊拱她。
- 使用者需要小心二驰,這些調(diào)用是異步的, tableview delegate有可能會在預(yù)緩存計算的時候不存在了,導(dǎo)致程序崩潰秉沼,所以使用者在tableview需要析構(gòu)的時候桶雀,在對應(yīng)的tableview controller的dealloc中講self.tableview.delegate = nil;唬复,確保delegate后續(xù)不會是一個野指針矗积。
UITableView+FDTemplateLayoutCell
至此,我們已經(jīng)分析了幾個子類的實現(xiàn)邏輯盅抚,唯一剩下一個分類漠魏,也是我們使用這個框架的入口 FDTemplateLayoutCell分類。全面了解這個組件近在咫尺妄均。
@interface UITableView (FDTemplateLayoutCell)
/* 為給定的重用標識符訪問內(nèi)部模板布局單元格柱锹。
* 一般來說,你不需要知道這些模板布局單元格丰包。
* @param identifier重用必須注冊的單元格的標識符禁熏。
*/
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier;
/* 返回由重用標識符指定并配置的類型的單元格的高度, 并通過block來配置。
* 單元格將被放置在固定寬度邑彪,垂直擴展的基礎(chǔ)上瞧毙,相對于其動態(tài)內(nèi)容,使用自動布局寄症。
* 因此宙彪,這些必要的單元被設(shè)置為自適應(yīng),即其內(nèi)容總是確定它的寬度給定的寬度等于tableview的寬度有巧。
* @param identifier用于檢索和維護模板的字符串標識符cell通過系統(tǒng)方法
* '- dequeueReusableCellWithIdentifier:'
* @param configuration用于配置和提供內(nèi)容的可選塊到模板單元格释漆。
* 配置應(yīng)該是最小的滾動性能足以計算單元格的高度。
*/
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration;
/* 計算的高度將通過其索引路徑進行高速緩存篮迎,當需要時返回高速緩存的高度男图,因此示姿,可以節(jié)省大量額外的高度計算。
* 無需擔心數(shù)據(jù)源更改時使緩存高度無效逊笆,它將在調(diào)用“-reloadData”或任何觸發(fā)方法時自動完成UITableView的重新加載栈戳。
* @param indexPath此單元格的高度緩存所屬的位置。
*/
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration;
/* 此方法通過模型實體的標識符緩存高度难裆。
* 如果你的模型改變子檀,調(diào)用“-invalidateHeightForKey:(id <NSCopying>)key”到無效緩存并重新計算,它比“cacheByIndexPath”方便得多差牛。
* @param key model entity的標識符命锄,其數(shù)據(jù)配置一個單元格。
*/
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id<NSCopying>)key configuration:(void (^)(id cell))configuration;
@end
@interface UITableView (FDTemplateLayoutHeaderFooterView)
/* 返回在具有重用標識符的表視圖中注冊的Header或Footer視圖的高度偏化。
* 在調(diào)用“ - [UITableView registerNib / Class:forHeaderFooterViewReuseIdentifier]”之后使用它脐恩,
* 與“-fd_heightForCellWithIdentifier:configuration:”相同。
* 它將調(diào)用“-sizeThatFits:”
* UITableViewHeaderFooterView的子類不使用自動布局侦讨。
*/
- (CGFloat)fd_heightForHeaderFooterViewWithIdentifier:(NSString *)identifier configuration:(void (^)(id headerFooterView))configuration;
@end
@interface UITableViewCell (FDTemplateLayoutCell)
/* 指示這是僅用于計算的模板布局單元格驶冒。
* 當配置單元格時,如果有非UI的副作用韵卤,你可能需要這個骗污。
* 類似:
* - (void)configureCell:(FooCell *)cell atIndexPath:(NSIndexPath *)indexPath {
* cell.entity = [self entityAtIndexPath:indexPath];
* if (!cell.fd_isTemplateLayoutCell) {
* [self notifySomething]; // non-UI side effects
* }
* }
*/
@property (nonatomic, assign) BOOL fd_isTemplateLayoutCell;
/* 啟用以強制此模板布局單元格使用“框架布局”而不是“自動布局”,
* 并且通過調(diào)用“-sizeThatFits:”來詢問單元格的高度沈条,所以你必須重寫這個方法需忿。
* 僅當要手動控制此模板布局單元格的高度時才使用此屬性
* 計算模式,默認為NO蜡歹。
*/
@property (nonatomic, assign) BOOL fd_enforceFrameLayout;
@end
- 先來看看我們平時開發(fā)中最頻繁調(diào)用的兩個方法
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration;
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id<NSCopying>)key configuration:(void (^)(id cell))configuration;
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration {
if (!identifier || !indexPath) {
return 0;
}
// Hit cache
if ([self.fd_indexPathHeightCache existsHeightAtIndexPath:indexPath]) {
return [self.fd_indexPathHeightCache heightForIndexPath:indexPath];
}
CGFloat height = [self fd_heightForCellWithIdentifier:identifier configuration:configuration];
[self.fd_indexPathHeightCache cacheHeight:height byIndexPath:indexPath];
return height;
}
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id<NSCopying>)key configuration:(void (^)(id cell))configuration {
if (!identifier || !key) {
return 0;
}
// Hit cache
if ([self.fd_keyedHeightCache existsHeightForKey:key]) {
CGFloat cachedHeight = [self.fd_keyedHeightCache heightForKey:key];
return cachedHeight;
}
CGFloat height = [self fd_heightForCellWithIdentifier:identifier configuration:configuration];
[self.fd_keyedHeightCache cacheHeight:height byKey:key];
return height;
}
- 這兩個方法屋厘,分別是對cell通過NSIndexPath 或者 key值 進行高度緩存,讀取高度的時候月而,先從緩存cache中讀取汗洒,如果緩存中沒有,在通過[self fd_heightForCellWithIdentifier:identifier configuration:configuration]方法進行計算高度并加入緩存中父款。
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration {
if (!identifier) {
return 0;
}
UITableViewCell *templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier];
//手動調(diào)用以確保與實際單元格的一致行為溢谤。 (顯示在屏幕上)
[templateLayoutCell prepareForReuse];
if (configuration) {
configuration(templateLayoutCell);
}
return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell];
}
- 通過blocks進行配置并計算cell的高度,主要通過[self fd_templateCellForReuseIdentifier:identifier]方法創(chuàng)建一個UITableViewCell的實例templateLayoutCell憨攒,最后再把templateLayoutCell放入[self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]中進行計算返回高度世杀。
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier {
NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier);
NSMutableDictionary<NSString *, UITableViewCell *> *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
if (!templateCellsByIdentifiers) {
templateCellsByIdentifiers = @{}.mutableCopy;
objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
UITableViewCell *templateCell = templateCellsByIdentifiers[identifier];
if (!templateCell) {
templateCell = [self dequeueReusableCellWithIdentifier:identifier];
NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier);
templateCell.fd_isTemplateLayoutCell = YES;
templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
templateCellsByIdentifiers[identifier] = templateCell;
[self fd_debugLog:[NSString stringWithFormat:@"layout cell created - %@", identifier]];
}
return templateCell;
}
- 將所有創(chuàng)建的templateCell放置在一個字典templateCellsByIdentifiers中,并通過runtime將其加入內(nèi)存中作為屬性肝集,實際上玫坛,templateCell 也是通過identifier在復(fù)用隊列中獲取復(fù)用的。所以包晰,cell在使用前湿镀,應(yīng)先注冊為cell的復(fù)用對象。
- 最后調(diào)用的[self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]進行高度計算伐憾。當然也是最關(guān)鍵的一個操作勉痴,既然這是一個高度計算的框架,那么計算的步驟當然是重中之重树肃。
- (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell *)cell {
CGFloat contentViewWidth = CGRectGetWidth(self.frame);
if (cell.accessoryView) {
//如果有定制accessoryView蒸矛,則去除這個寬度
contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
} else {
//如果有系統(tǒng)accessoryView展示,則去除對應(yīng)的寬度胸嘴。
static const CGFloat systemAccessoryWidths[] = {
[UITableViewCellAccessoryNone] = 0,
[UITableViewCellAccessoryDisclosureIndicator] = 34,
[UITableViewCellAccessoryDetailDisclosureButton] = 68,
[UITableViewCellAccessoryCheckmark] = 40,
[UITableViewCellAccessoryDetailButton] = 48
};
contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
}
CGFloat fittingHeight = 0;
if (!cell.fd_enforceFrameLayout && contentViewWidth > 0) {
//如果是自動布局雏掠,則將contentView的寬度約束添加進去。
//這樣做的目的是讓UILabel之類的內(nèi)容可能多行的控件適應(yīng)這個寬度折行(當然前提是我們已經(jīng)設(shè)置好了這些控件的布局約束)劣像。然后調(diào)用systemLayoutSizeFittingSize來計算高度乡话。
//最后移除剛才臨時添加的contentView寬度約束。
NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth];
[cell.contentView addConstraint:widthFenceConstraint];
fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
[cell.contentView removeConstraint:widthFenceConstraint];
}
if (fittingHeight == 0) {
// 嘗試調(diào)用 '- sizeThatFits:'進行高度計算.
// 注意:配件高度不應(yīng)包括分隔線視圖高度耳奕。
fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height;
}
// 進行完前面的邏輯后高度如果仍然為0绑青,則使用默認行高44
if (fittingHeight == 0) {
fittingHeight = 44;
}
// 添加一像素作為tableView分割線高度。
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
fittingHeight += 1.0 / [UIScreen mainScreen].scale;
}
return fittingHeight;
}
至此屋群,就大致將這個框架分析的差不多了闸婴,源碼中,對類的實例化均為采用runtime添加AssociatedObject的方式芍躏。就不做解釋了邪乍。
最后
賞花不忘種花人,把作者關(guān)于這個框架優(yōu)秀的性能分析復(fù)習(xí)下对竣。
-
地址:http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/
?