Intro
UITableView
是個UIScrollView
,而UIScrollView
需要知道contenSize
才能根據(jù)bounds
,contentInset
,contentOffset
共同決定是否可以滑動以及滾動條的長度和位置.因此,UITableView
需要知道自己需要創(chuàng)建的cell
的個數(shù)和每個cell
的高度,才能得到最終的contentSize
.
iOS7
估算機制
在iOS7及iOS7以下的系統(tǒng)中,當tableView要展示時,會調(diào)用代理的數(shù)據(jù)源方法- tableView: heightForRowAtIndexPath:
(tableView獲取高度默認訪問rowHeight屬性,實現(xiàn)了這個方法后,rowHeight屬性就會失效,所以對于等高的cell來說, rowHeight能夠減少許多不必要的計算和方法調(diào)用的開銷)獲得所有row
的高度以確定scrollView
的contentSize
.
- tableView: heightForRowAtIndexPath:
適用于不等高cell(沒實現(xiàn)該數(shù)據(jù)源方法就使用rowHeight
屬性值)
rowHeight
適用于等高cell (rowHeight在iOS7中默認為44.f,iOS8為-1.f,即UITableViewAutomaticDimension)
以上兩種用于計算cell的高度的方法,都是在tableView將要顯示的時候,一次性計算出所有cell.這個時候,如果所展示的cell很多,并且每個cell的高度也很高,那么耗費在計算高度的初始時間將會多很多,所以蘋果在iOS7的時候推出了estimatedRowHeight
這個屬性(與之類似的還有estimatedSectionHeaderHeight
,estimatedSectionFooterHeight
).
顧名思義,這個屬性的意義在于估算.有了這個估算高度,就能確定contenSize
啦,但是這個contenSize
是個估算值,是通過estimatedRowHeight
xcell的個數(shù)
得到初始的contenSize
中的高度,并不是最終的contenSize
.實現(xiàn)了這個屬性后,tableView就不會一次性計算所有的cell的高度了,只會計算當前屏幕能夠顯示的cell個數(shù)加上幾個.滑動時,tableView不停地得到新的cell,調(diào)用數(shù)據(jù)源方法得到高度,更新自己的contenSize
,在滑到最后的時候,會得到正確的contenSize
,在這過程中,旁邊的滾動條會不可避免地"抖動",因為contenSize
在不停地更新.因此,在設(shè)置estimatedRowHeight
時要給出盡量接近cell高度平均值的數(shù)值,讓"抖動"更小.
// 從打印能看出,contentSize會按照一定的算法進行更新
self.tableView.estimatedRowHeight = 100;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"%@",NSStringFromCGSize(tableView.contentSize));
return 300;
}
此外,iOS7中每個cell的高度會被系統(tǒng)自動緩存起來,不會再重復(fù)計算了.
高度計算
在iOS7上,設(shè)置一個cell的尺寸有兩種方式:
- AutoLayout
- Manual-sizing code (即人工手算)
第二種方式比較常見,通常我們通過在- tableView:cellForRowAtIndexPath
代理方法中將模型賦值給cell,cell通過模型計算高度,并將高度的計算結(jié)果存儲在模型身上,然后在tableView: heightForRowAtIndexPath:
方法中用cell模型中的高度屬性獲得高度.
其實第一種方式也很方便,最需要注意的是要設(shè)置好完整的約束.系統(tǒng)提供給了我們一個API叫- systemLayoutSizeFittingSize:
,可以通過約束計算到cell的高度.
// 設(shè)置一個屬性,或者是靜態(tài)的全局變量的cell.因為這個cell是為了計算高度而生,不必每次調(diào)用方法都創(chuàng)建.
@property (nonatomic, strong) XXXTableViewCell *prototypeCell;
// 初始化時
self.prototypeCell = (XXXTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:@"XXXTableViewCell"];
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// 你或許會想到調(diào)用- tableView:cellForRowAtIndexPath方法拿到當前indexPath的cell,然后通過systemLayoutSizeFittingSize方法來計算高度.可是顯示tableView之前只有拿到高度才能創(chuàng)建cell實例.如果在這里面調(diào)用該方法,會死循環(huán).
XXXTableViewCell *cell = self.prototypeCell;
cell.model = [self.modelArray objectAtIndex:indexPath.row];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
// 為什么要加上一個奇怪的數(shù)呢? 因為這里計算的只是cell的contentView的高度,分割線還占1個像素的高度.
return 1.0 / [UIScreen mainScreen].scale + size.height;
}
至于1個像素為什么是這樣子計算,請看我的另一篇文章:<iOS中線寬與像素的關(guān)系>. 不那么嚴謹?shù)脑?給個1也沒什么大問題.
另外需要注意的是,在iOS7下(iOS8沒有這個問題),如果布局中有UILabel
,并且行數(shù)大于0時,需要指定preferredMaxLayoutWidth
,這樣Label才能知道自己什么時候該換行,然后- systemLayoutSizeFittingSize
才能得到正確的高度.另一種方案就是,給cell的contentView
添加一個和tableView寬度相同的寬度約束,這樣就能在UILabel
約束完備的情況下算出UILabel
的寬度.(因為contentView
寬度的計算需要知道子控件寬度的累加,而UILabel
的換行卻依賴著contentView
的寬度,不換行就不知道UILabel
的高度!)
iOS8
高度計算與iOS7不同的地方
iOS8的變化不小,提出了self-sizing cell
的概念,認為cell是可以隨時被改變高度的.self-sizing cell
對于支持Dynamic Type
非常有用厘熟。你可能沒聽說過Dynamic Type
旗扑,但你可能見過系統(tǒng)的“Settings”屏幕:
"Dynamic Type最初由iOS 7引入,允許用戶自定義文本大小從而滿足app的需要晶默。不過僅有采用Dynamic Type的app才能響應(yīng)文本的改變读存,可能只有一小部分第三方應(yīng)用使用了該功能为流。
從iOS 8開始窜醉,蘋果想要鼓勵開發(fā)者使用Dynamic Type。正如在WWDC session中提到的那樣艺谆,所有蘋果系統(tǒng)級應(yīng)用都使用了Dynamic Type榨惰,并且內(nèi)置的標簽已經(jīng)有了動態(tài)字體。當用戶改變文本大小時静汤,這些標簽也會改變其大小琅催。
更進一步說,Self Sizing Cell的引入是促進Dynamic Type使用的辦法虫给,它可以節(jié)省大量寫代碼調(diào)整行高的時間藤抡。如果單元格可以自動調(diào)整了,那么使用Dynamic Type就很顯而易見了抹估。
你只需要從尺寸固定的自定義字體中將字體更改為文本類型(比如標題和內(nèi)容主體)首選的字體缠黍。也就是說當你運行app時,它會適應(yīng)文本大小的改變药蜻。"
對于tableView來說,self-sizing cell
帶來的變化就是cell的高度不再被緩存起來了.在iOS7中,當tableView滑到最底下時,所有的cell的高度都被經(jīng)過計算,之后不管怎么滑,都不會進行額外的計算了.而iOS8,不管怎樣都會重新計算cell的高度,因此對于性能的影響較大.如果對程序性能要求較高,就需要設(shè)計一套緩存機制,將每個cell的高度緩存起來.
自動算高
另外,self-sizing cell
的另外一個重要功能就是實現(xiàn)了自動算高,不過需要滿足2個條件:
- 使用Autolayout進行布局
- 設(shè)置
estimatedRowHeight
的值
然后就OK了.
參考文章
http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/
https://github.com/xhzengAIB/iOS8SelfSizingCells