UITableViewCell 高度計(jì)算從混沌初始到天地交泰

UITableViewCell 高度計(jì)算從混沌初始到天地交泰


本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:


UITableView控件可能是iOS中大家最常用的控件了(滾動(dòng)視圖、cell重用、卡頓優(yōu)化),今天要討論的不是這些高大上的話題,今天的話題只是cell高度的計(jì)算。

* 傳統(tǒng)frame布局下UITableViewCell 高度計(jì)算

* AutoLayout下UITableViewCell高度計(jì)算(iOS6、7)

* UITableViewCell高度計(jì)算之iOS8抽風(fēng)之旅

* UITableViewCell高度計(jì)算之大一統(tǒng)

*第三方庫UITableView-FDTemplateLayoutCell源碼拋析

以下demo都是在cell高度可變的基礎(chǔ)上進(jìn)行的

一、傳統(tǒng)frame布局下UITableViewCell 高度計(jì)算

? ? ? ? 1、史上最傳統(tǒng)的UITableViewCell使用方法(號(hào)稱又笨又老),相信大家都用過這種,純frame布局,cell定制,手動(dòng)傳入數(shù)據(jù)通過手動(dòng)計(jì)算每一行cell的高度,代碼都不好意思上了。

還是上下之前的demo吧!

01-UITableViewCell-frame

主要是在UITableViewCell(subCell)中使用一個(gè)靜態(tài)方法傳入數(shù)據(jù)并手動(dòng)計(jì)算內(nèi)容的高度

說到手動(dòng)計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對(duì)應(yīng)的寬高,看下具體的API:

@interface NSString(UIStringDrawing)

// Single line, no wrapping. Truncation based on the NSLineBreakMode.

- (CGSize)sizeWithFont:(UIFont*)fontNS_DEPRECATED_IOS(2_0,7_0,"Use -sizeWithAttributes:");

- (CGSize)sizeWithFont:(UIFont*)font forWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");

// Wrapping to fit horizontal and vertical size. Text will be wrapped and truncated using the NSLineBreakMode. If the height is less than a line of text, it may return

// a vertical size that is bigger than the one passed in.

// If you size your text using the constrainedToSize: methods below, you should draw the text using the drawInRect: methods using the same line break mode for consistency

- (CGSize)sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)sizeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// Uses NSLineBreakModeWordWrap

- (CGSize)sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// NSTextAlignment is not needed to determine size

這個(gè)地方Apple提供了一個(gè)NSString的分類,我們可以通過傳入對(duì)應(yīng)的string 計(jì)算出label的自適應(yīng)寬高,說到底就是使用sizeWithFont:系列重載函數(shù)根據(jù)字符串計(jì)算label的content大小。

代碼中使用:

(NSString一個(gè)傳統(tǒng)的方法sizeWithFont:)來計(jì)算label新的frame,然后更新布局,之后返回一個(gè)預(yù)計(jì)算的高度值

+ (CGFloat)calulateHeightWithtTitle:(NSString*)title

{

CGFloatheight =20;

CGSizelabelSize = [titlesizeWithFont:[UIFont ? systemFontOfSize:17] constrainedToSize:CGSizeMake(300,500)];

height = height + labelSize.height;

returnheight;

}

最終方法的調(diào)用在:

- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath

{

? ? ? ?return [HomeCell ?calulateHeightWithtTitle:self.dataArray[indexPath.row]];

}

來完成,并且return該float值作為cell的高度。

二、AutoLayout下UITableViewCell高度計(jì)算(iOS6、7)

1、下面介紹第二種方法,使用自動(dòng)布局下的cell高度計(jì)算,總體來講,自動(dòng)布局下 的cell高度計(jì)算歸功于UILabel的布局,自動(dòng)布局下默認(rèn)無需要再指定view的frame,設(shè)置完對(duì)應(yīng)的約束,label會(huì)自動(dòng)根據(jù)內(nèi)容的多少來完成布局。廢話少說先上體驗(yàn)版demo。

08-AutoLayoutCellHeight_ios7

上面描述到,傳統(tǒng)frame布局時(shí)間,主要是通過一些列手手動(dòng)計(jì)算cell中l(wèi)abel的寬高,然后在針對(duì)cell中的subView進(jìn)行重新布局,最后得出一個(gè)整體的高度作為cell真實(shí)的高度,那么在自動(dòng)布局中又該如何實(shí)現(xiàn)呢?首先自動(dòng)布局一改了之前frame的概念,自動(dòng)布局中不存在所謂的坐標(biāo) 寬高,只有對(duì)應(yīng)的約束。針對(duì)UILabel來說,自動(dòng)布局下label會(huì)根據(jù)內(nèi)容的多少自適應(yīng)的調(diào)整label的大小,顯示對(duì)應(yīng)的內(nèi)容。這一點(diǎn)先看下UILabel在iOS6以后發(fā)生的變化:

// Support for constraint-based layout (auto layout)

// If nonzero, this is used when determining -intrinsicContentSize for multiline labels

@property(nonatomic)CGFloat ?preferredMaxLayoutWidthNS_AVAILABLE_IOS(6_0);

看到官方的注視,基本也大概有差不多的意思了,這東西實(shí)在autolayout下使用的,大概意思是給多行l(wèi)abel設(shè)置一個(gè)布局時(shí)間優(yōu)先使用的一個(gè)寬度。

在看下UIView的變化

@interfaceUIView (UIConstraintBasedLayoutFittingSize)

/* The size fitting most closely to targetSize in which the receiver's subtree can be laid out while optimally satisfying the constraints. If you want the smallest possible size, pass UILayoutFittingCompressedSize; for the largest possible size, pass UILayoutFittingExpandedSize.

Also see the comment for UILayoutPriorityFittingSizeLevel.

*/

- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSizeNS_AVAILABLE_IOS(6_0);

// Equivalent to sending -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: with UILayoutPriorityFittingSizeLevel for both priorities.

意思大概就是說 當(dāng)前的這個(gè)這個(gè)尺寸關(guān)系能夠最佳的適應(yīng)接收器的子樹在滿足自適應(yīng)約束的同時(shí),如果想要一個(gè)最下的尺寸就設(shè)置為:UILayoutFittingCompressedSize;反之設(shè)置:UILayoutFittingExpandedSize。

實(shí)戰(zhàn)應(yīng)用:

自動(dòng)布局下的自適應(yīng)cell高度玩轉(zhuǎn),本教程完全依賴storybord ,依舊在代碼UI領(lǐng)域的媛猿們,需要轉(zhuǎn)變一下思維了。

(1)、創(chuàng)建故事板、初始化好tableview、cell的輸出口等,準(zhǔn)備cell的約束,如圖:

cell上只有一個(gè)label,label的約束如下,大體就是具體上下左右各加上一個(gè)約束,將來在label中放在對(duì)應(yīng)的內(nèi)容文字,自適應(yīng)高度(不要忘了設(shè)置cell的identifier)。

(2)、部分實(shí)現(xiàn)處理代碼

ViewController中部分代理方法

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

{

return1;

}

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section

{

? ? ? ?return [self.dataArraycount];

}

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath

{

static NSString *cellIdentifier = @"HomeCell";

HomeCell *cell = [tableView ?dequeueReusableCellWithIdentifier:cellIdentifier];

cell.content.text= [self.dataArray ?objectAtIndex:indexPath.row];

CGFloat ?preMaxWaith =[UIScreen ?mainScreen].bounds.size.width-108;

[cell.contentset ?PreferredMaxLayoutWidth:preMaxWaith];

[cell.contentlayout ?IfNeeded];

returncell;

}

-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath

{

staticHomeCell*cell =nil;

static dispatch_once_ ?tonceToken;

//只會(huì)走一次

dispatch_once(&onceToken, ^{

cell = (HomeCell*)[tableView ?dequeueReusableCellWithIdentifier:@"HomeCell"];

});

//calculate

CGFloatheight = [cell ?calulateHeightWithtTitle:[self.dataArray objectAtIndex:indexPath.row]desrip:[self.dataArray objectAtIndex:indexPath.row]];

returnheight;

}

HomeCell.m


-(CGFloat)calulateHeightWithtTitle:(NSString*)title desrip:(NSString*)descrip

{

//這里非常重要

CGFloat preMaxWaith =[UIScreen mainScreen].bounds.size.width-108;

[self.contentset PreferredMaxLayoutWidth:preMaxWaith];

//[self.titleLabel setText:title];

//這也很重要

[self.content ?layoutIfNeeded];

[self.content ?setText:descrip];

[self.contentView ?layoutIfNeeded];

CGSizesize = [self.contentView ?systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

//加1是關(guān)鍵

returnsize.height+1.0f;

}


自動(dòng)布局版cell高度計(jì)算OK??!

三、UITableViewCell高度計(jì)算之iOS8抽風(fēng)之旅

1、說到iOS8,在iOS8下如果要計(jì)算cell的高度,代碼越來越少,工作越來越輕松,殊不知表面看起來特別人性的iOS8背地里面也有太多坑的勾當(dāng)(具體原因見后面解釋)。

先上iOS的計(jì)算cell高度的體驗(yàn)demo:

02-AutoLayout-iOS8-

iOS8下計(jì)算cell高度的工作比起之前的版本更加輕松

(1)、故事版拖好對(duì)應(yīng)的VC、cell,接下來上約束,約束如下:


整體來說與2中的約束差不多,分別設(shè)置label距離四周的約束情況。(本篇文章要實(shí)現(xiàn)的本來就是相同的效果,在不同版本下的的實(shí)現(xiàn)方式以及優(yōu)劣的對(duì)比與優(yōu)化。)

設(shè)置好約束后

(2). iOS8的cell高度計(jì)算代碼

設(shè)置tableview的屬性

self.tableView.estimatedRowHeight=44.0;

self.tableView.rowHeight=UITableViewAutomaticDimension;

至此,iOS8cell高度自適應(yīng)計(jì)算OK!! 就是這么簡(jiǎn)單...

四、UITableViewCell高度計(jì)算之大一統(tǒng)


在介紹本欄目之前先上一張表:

heightForRowAtIndexPath:cell高度計(jì)算次數(shù)

heightForRowAtIndexPath:cell計(jì)算對(duì)比

由于iOS7之后,tableview 提供了estimatedHeightForRowAtIndexPathCount的API,這就對(duì)cell高度計(jì)算的方法調(diào)用次數(shù)產(chǎn)生了影響。

下面首先說下estimatedHeightForRowAtIndexPathCount :

// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.

// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.

- (CGFloat)tableView:(UITableView*)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(7_0);

? ? ? ?apple的文檔里面說大概意思是estimatedHeight可以快速的預(yù)估一個(gè)cell的高度值,從而讓table的加載速度更快。整體來說就是tableview在渲染的時(shí)間,他會(huì)首先根據(jù)內(nèi)容計(jì)算每個(gè)cell的高度,從而計(jì)算出tableview的的一個(gè)contentsize(tableview繼承于scrollview),但是如果有一萬行數(shù)據(jù),那么這個(gè)計(jì)算的過程會(huì)非常的卡頓,從而影響table的load的速度,我們可以給cell(除了當(dāng)前需要顯示在屏幕上的cell)設(shè)置一個(gè)預(yù)估的高度值,這樣就大大節(jié)省了計(jì)算高度的損耗與開銷。

有上圖可以看出,iOS7 的tableview對(duì)于cell的高度是有緩存功能的,當(dāng)劃到底部,從底部再往頂部滑動(dòng)時(shí)間,heightForRowAtIndexPath:cell的調(diào)用次數(shù)為0,這說話cell的高度已經(jīng)換存在了內(nèi)存。iOS8、iOS9坑爹的一面在于當(dāng)關(guān)閉高度預(yù)估方法時(shí)間(estimatedHeightForRowAtIndexPathCount),heightForRowAtIndexPath:cell的調(diào)用次數(shù)非常多(我們一般會(huì)在這個(gè)地方計(jì)算根據(jù)內(nèi)容手動(dòng)計(jì)算cell的高度,或者更新cell內(nèi)不各種view的約束),這個(gè)過程如果頻繁的調(diào)用是非常耗損性能的,更悲劇的事造成tableview的卡頓,這個(gè)是最容忍不了的。當(dāng)開啟高度預(yù)估時(shí)間,高度預(yù)估之時(shí)返回了一個(gè)定值,此時(shí)heightForRowAtIndexPath:cell的調(diào)用次數(shù)大大減少,高度計(jì)算的工作也就大大減少,此時(shí)就是我們想要的效果。

此外,這個(gè)地方有一個(gè)可能忽略的問題,當(dāng)我們的工程從原來的iOS7遷移到8再到9的時(shí)間,如果這個(gè)地方不做進(jìn)一步的優(yōu)化,之前的代碼在新的系統(tǒng)下跑起來結(jié)果就不想而之了,為了能夠兼容到所有系統(tǒng)下的cell高度計(jì)算,推薦一個(gè)新的工具

UITableView-FDTemplateLayoutCell

參考博客:具體教程如

(1)、FDTemplateLayoutCell 使用教程

1. 下載FDTemplateLayoutCell第三方庫,導(dǎo)入工程

2. 導(dǎo)入頭文件?

3. 使用FD實(shí)現(xiàn)heightForRowAtIndexPath方法,如下:

4、大功告成,使用fd一次性解決的iOS 6、7、8、9中的cell高度計(jì)算問題,F(xiàn)D采用自帶的緩存的機(jī)制,無需多次調(diào)用heightForRowAtIndexPath時(shí)間的cell高度計(jì)算開銷

1、FDTemplateLayoutCell之所以能夠做到兼容到所有的系統(tǒng)版本下的tableview,主要在于它維護(hù)了一套自己的cell高度緩存,同時(shí)有效的利用了tableview的高度預(yù)估的功能。從新定義新的cell高度緩存策略,這一點(diǎn)解決了只有iOS7下系統(tǒng)才會(huì)主動(dòng)緩存cell高度的這一難點(diǎn),有了FDiOS8、9下也能使用緩存高度

2、開啟UITableView高度預(yù)估功能,優(yōu)化heightForRowAtIndexPath的調(diào)用累計(jì)次數(shù)

(tableView:estimatedHeightForRowAtIndexPath: NS_AVAILABLE_IOS(7_0);)

由上可以看出estimatedHeightForRowAtIndexPath是iOS7才有的,iOS6是沒有這個(gè)代理的,這個(gè)時(shí)間不僅要問,難道要iOS必須支持iOS7+以上才能使用,答案當(dāng)然不是,系統(tǒng)的API早已做了優(yōu)化,estimatedHeightForRowAtIndexPath在iOS6下面默認(rèn)是可以被忽略的,不會(huì)因?yàn)榘姹镜膯栴}引起異常。在iOS6下高度計(jì)算的策略會(huì)跟iOS8、9下有點(diǎn)類似,使用FD自己提供的緩存,也能達(dá)到有效的減少計(jì)算cell高度帶來的開銷。

五、FDTemplateLayoutCell源碼拋析

談到FD,首先熟悉下之前的一個(gè)知識(shí)點(diǎn),?iOS知識(shí)點(diǎn)整理-RunLoop??赡苡行├仙U劻?,也有可能部分童鞋看到直接暈掉了,其實(shí)大多iOS里面大多第三方庫的手段無外乎就是runtime(這個(gè)東西在java中叫reflact,java里面有AOP , iOS 其實(shí)跟這個(gè)差不多)、CF這些黑魔法之類的東西來進(jìn)行偷天換日、移花接木。

小結(jié):iOS 中的runloop

? ? ? ? 1、NSRunLoop提供了面向?qū)ο蟮腁PI,但這些API不是線程安全的

? ? ? ? 2、CFRunLoopRef提供了純C函數(shù)的API,所有這些API都是線程安全的

NSRunLoop是cocoa提供的,這個(gè)東西可能大多人還是經(jīng)常使用的,cell里面更新異步下載成功的圖片、啟用一個(gè)timer追加到當(dāng)前的應(yīng)用循環(huán)中、啟用一個(gè)常駐線程等;

CFRunLoopRef可能就相對(duì)陌生些,CF開頭跟定就是CoreFoundation中定義的,可以暫時(shí)理解為每個(gè)線程都有一個(gè)對(duì)應(yīng)的runloop, 在一個(gè)runloop中可以有多種Model(模式),每個(gè)Mode又包含若干個(gè)source/Timer/Observe .

程序執(zhí)行的時(shí)間當(dāng)前runloop 只能存在一種Model,如果發(fā)生場(chǎng)景切換需要退出當(dāng)前Model,進(jìn)入下一個(gè)Model

系統(tǒng)一共提供了五種model:

? ? ? ? ?NSDefaultRunLoopMode: ? ?App默認(rèn)Mode,當(dāng)沒有接收到ScrollView滾動(dòng)是,主線程通常使用這個(gè)Mode

? ? ? ? ?NSTrackingRunLoopMode: ?到接收到ScroolView或其子類的時(shí)候,主線程就會(huì)切換到這個(gè)模式下運(yùn)行。

? ? ? ? ?UIInitializationRunLoopMode:當(dāng)App啟動(dòng)時(shí)使用的第一個(gè)Mode,當(dāng)啟動(dòng)完成后不再使用。

? ? ? ? ? NSRunLoopCommonModes,是一個(gè)tag,本質(zhì)上不是一個(gè)Mode,默認(rèn) ? ? ? ? ? ? ? ? ? ?NSDefaultRunLoopMode和NSTrackingRunLoopMode都綁定這個(gè)tag。(應(yīng)用場(chǎng)景:有時(shí)候我們需要添加一個(gè)NSTimer在RunLoop,在這時(shí)需要制定一個(gè)Modes,現(xiàn)在的需求是:我們既要在默認(rèn)模式下要監(jiān)聽,在滾動(dòng)模式下也要監(jiān)聽,但只能制定一個(gè)模式,這是可以制定這個(gè)CommonMode)

? ? ? ? ?GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部的Mode,通常做不到。

處理不同事件使用不同的Mode,可以最大限度的把性能的最大化處理不同分類的事件,提高性能。

知道了這些,我們可以在此處做文章,我們發(fā)現(xiàn)UITableView(繼承UIScrollView)不滾動(dòng)時(shí)間是NSDefaultRunLoopMode 模式,滾動(dòng)時(shí)間是NSTrackingRunLoopMode模式,我們可以 通過注冊(cè)觀察者來實(shí)現(xiàn)讓tableview不滾動(dòng)的時(shí)間再去計(jì)算所有的cell的高度,一旦當(dāng)tableview開始滾動(dòng)我們?cè)偃ト〉脮r(shí)間著時(shí)間緩存池里面已經(jīng)計(jì)算 的差不多了,也就是說盡最大可能讓tableview不滾動(dòng)的時(shí)間處理好所有的cell高度,緩存下來,等到滑動(dòng)tableview的時(shí)間優(yōu)先從緩存取,這個(gè)地方盡最大避免了邊滑動(dòng)邊計(jì)算cell高度卡頓問題。

完成了這個(gè)知識(shí)點(diǎn),接下來就是處理好緩存邏輯的事情了。

1、首先對(duì)于FD來說,維護(hù)cell的高度需要將計(jì)算過的cell的高度放進(jìn)一個(gè)二維數(shù)組里面(section row)

? ? FD中存在一個(gè)可維護(hù)的NSMutableArray sections; 可以先理解為一個(gè)嵌套起來的數(shù)組是一個(gè)二位的數(shù)組,接下來的時(shí)間會(huì)把tableview 某個(gè)section下的row對(duì)應(yīng)的行對(duì)應(yīng)的高度存在這個(gè)位置,

2、tableView渲染的時(shí)間,統(tǒng)一還是會(huì)走 heightForRowAtIndexPath方法的,我們只需要在此處直接獲取到cache里面的已經(jīng)存儲(chǔ)的高度就行了,在此處避開cell的高度邏輯計(jì)算過程就到達(dá)了我們的目的。

FD組件已經(jīng)作了很好的封裝,在heightForRowAtIndexPath中調(diào)用fd計(jì)算高度的方法,

- (CGFloat)fd_heightForCellWithIdentifier:(NSString*)identifier cacheByIndexPath:(NSIndexPath*)indexPath configuration:(void(^)(id))configuration

{

if(!identifier || !indexPath) {

return0;

}

// Enable auto cache invalidation if you use this "cacheByIndexPath" API.

if(!self.fd_autoCacheInvalidationEnabled) {

self.fd_autoCacheInvalidationEnabled=YES;

}

// Enable precache if you use this "cacheByIndexPath" API.

if(!self.fd_precacheEnabled) {

self.fd_precacheEnabled=YES;

// Manually trigger precache only for the first time.

[selffd_precacheIfNeeded];

}

// Hit the cache

if([self.fd_cellHeightCachehasCachedHeightAtIndexPath:indexPath]) {

[selffd_debugLog:[NSStringstringWithFormat:

@"hit cache - [%@:%@] %@",

@(indexPath.section),

@(indexPath.row),

@([self.fd_cellHeightCachecachedHeightAtIndexPath:indexPath])]];

return[self.fd_cellHeightCachecachedHeightAtIndexPath:indexPath];

}

// Call basic height calculation method.

CGFloatheight = [selffd_heightForCellWithIdentifier:identifierconfiguration:configuration];

[selffd_debugLog:[NSStringstringWithFormat:

@"cached - [%@:%@] %@",

@(indexPath.section),

@(indexPath.row),

@(height)]];

// Cache it

[self.fd_cellHeightCachecacheHeight:heightbyIndexPath:indexPath];

returnheight;

}

這個(gè)步驟中,基本可以看出FD的使用策略,首先開啟一個(gè)[selffd_precacheIfNeeded]的操作(這個(gè)過程是做了一個(gè)預(yù)計(jì)算cell高度的操作,稍后詳解),接下來的過程就是從緩存池中根據(jù)IndexPath(cell高度預(yù)存儲(chǔ)在一個(gè)模擬的二維數(shù)組中)去讀取cell的高度,如果cache命中就直接返回cell高度,否則執(zhí)行:

// Call basic height calculation method.

CGFloatheight = [selffd_heightForCellWithIdentifier:identifierconfiguration:configuration];

去手動(dòng)計(jì)算一次cell的高度,計(jì)算獲得后存入緩存池

// Cache it

[self.fd_cellHeightCachecacheHeight:heightbyIndexPath:indexPath];

最后返回高度。

3、介紹FD的緩存池

FD在這個(gè)地方利用了runloop的黑魔法,通過注冊(cè)一個(gè)觀察者,當(dāng)tableview停止滑動(dòng)的他會(huì)主動(dòng)去計(jì)算當(dāng)前數(shù)據(jù)源中的剩余的cell的高度,計(jì)算完以后存儲(chǔ)在緩存池中,這個(gè)調(diào)用就是(2)中的

// Enable precache if you use this "cacheByIndexPath" API.

if(!self.fd_precacheEnabled) {

self.fd_precacheEnabled=YES;

// Manually trigger precache only for the first time.

[selffd_precacheIfNeeded];

}

在這個(gè)開啟調(diào)用中,通過一些列手段將tableview不滾動(dòng)時(shí)間去計(jì)算cell的高度(具體原理此處省略),計(jì)算后存入緩存池sections,sections是一個(gè)可變數(shù)組,筆者顯示把這個(gè)理解成一個(gè)內(nèi)存存儲(chǔ)元素是可變數(shù)組的數(shù)組(模擬一個(gè)二維數(shù)組),F(xiàn)D先是給自己增加了一個(gè)屬性sections作為緩存池,通過objc_setAssociatedObject給分類增加屬性的此處就不介紹了,

[selfbuildHeightCachesAtIndexPathsIfNeeded:@[indexPath]];

self.sections[indexPath.section][indexPath.row] =@(height);


// Build every section array or row array which is smaller than given index path.

[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath*indexPath,NSUIntegeridx,BOOL*stop) {

NSAssert(indexPath.section>=0,@"Expect a positive section rather than '%@'.",@(indexPath.section));

for(NSIntegersection =0; section <= indexPath.section; ++section) {

if(section >=self.sections.count) {

self.sections[section] =@[].mutableCopy;

}

}

NSMutableArray*rows =self.sections[indexPath.section];

for(NSIntegerrow =0; row <= indexPath.row; ++row) {

if(row >= rows.count) {

rows[row] =@(_FDTemplateLayoutCellHeightCacheAbsentValue);

}

}

}];


此處主要是構(gòu)造一個(gè)緩存池,通過在sections中存儲(chǔ)一個(gè)NSMutableArray,模擬一個(gè)二維的數(shù)組

通過indexPath的section 和 row作為下標(biāo),構(gòu)造完成直接將高度存進(jìn)去:

self.sections[indexPath.section][indexPath.row] =@(height);

至此緩存池結(jié)束

4、至此FD的核心手段大題已經(jīng)講完,接下來就是考慮到tableview的增刪改插的時(shí)間的處理問題,這一系列的動(dòng)作都會(huì)對(duì)緩存池的更新有一定的影響,F(xiàn)D已經(jīng)做了最大的限度的優(yōu)化,依舊runtime, swizzling的魔法就不多解釋了。

dispatch_once(&onceToken, ^{

SELselectors[] = {

@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(NSUIntegerindex =0; index

SELoriginalSelector = selectors[index];

SELswizzledSelector =NSSelectorFromString([@"fd_"stringByAppendingString:NSStringFromSelector(originalSelector)]);

MethodoriginalMethod =class_getInstanceMethod(self, originalSelector);

MethodswizzledMethod =class_getInstanceMethod(self, swizzledSelector);

method_exchangeImplementations(originalMethod, swizzledMethod);

}

});


小結(jié):

FD是一個(gè)封裝的很完美的庫,其實(shí)從一開始使用這個(gè)庫就喜歡上了,作者是百度的sunnyxy,另一方面iOS中runtime仍舊是一個(gè)很強(qiáng)大的東西,大多的第三方庫無非都是基本objc runtime做的一些便捷優(yōu)化,但是一個(gè)優(yōu)秀的第三方庫需要作者不斷的完善和大家的共同努力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容