我們經(jīng)常會遇到UITableViewCell的高度要跟隨內(nèi)容而調(diào)整,,在未引入AutoLayout之前,,我們使用以下方法計算Label高度,然后heightForRowAtIndexPath中返回計算的高度,,這種做法,,真的很土很局限很不好,如果UILabel使用了CoreText或者UIKit進(jìn)行了富文本不同字體的排版,,它更是沒辦法,,我還得分段來計算,總之各種麻煩,。
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakMode NS_DEPRECATED_IOS(2_0, 7_0, "Use -boundingRectWithSize:options:attributes:context:"); // NSTextAlignment is not needed to determine size
本系列文章我們討論的是AutoLayout,,那iOS6引入AutoLayout之后,情況是否有所變化呢?當(dāng)然,!而且AutoLayout在iOS不斷更新過程中,,也在一起不斷的優(yōu)化,以方便開發(fā)者進(jìn)行布局,。說實話,,跟很多開發(fā)者一樣,我目前也并不是特別喜歡AutoLayout,,有一些不可控的因素,,布局并沒有完全掌握在自己手上,需要依賴系統(tǒng)根據(jù)約束進(jìn)行調(diào)整,,這讓保守的開發(fā)人員很沒有安全感,。不廢話了,我們還是進(jìn)入到正題,。
我們直接拿一個現(xiàn)實的需求來進(jìn)行討論,,如下圖,我們需要構(gòu)建一個頁面,,上面部分顯示我們預(yù)約保養(yǎng)的基本信息,,下面部分顯示該門店目前提供的優(yōu)惠券列表,這種需求最簡單的做法就是直接用兩種UITableViewCell,,下面的部分的UITableViewCell要簡單一些,,高度固定,而上面部分的UITableViewCell的內(nèi)容有很多不確定的內(nèi)容,,比如用戶預(yù)約保養(yǎng)選擇的項目,,門店的名稱地址,這些UILabel的高度都不確定,,所以導(dǎo)致上面部分的UITableViewCell的高度需要動態(tài)調(diào)整,,這是一個比較典型的實例,我們一起來看一下如何解決,。
一,、建立合理的約束
我們先建立自定義Cell: AppointmentedInfoCell (創(chuàng)建XIB)。
然后設(shè)置合理的約束條件,,什么是合理的約束條件,,一方面我們需要按前面講到的設(shè)置正確的約束條件,另一方面我的意思主要是控件的compression resistance 和 hugging constraints ,,在IB中如下圖:
我們知道在Autolayout中,,我們的UILabel,UIButton等控件都有了內(nèi)建大小(intrinsic content size),,就是說控件的大小會根據(jù)內(nèi)容進(jìn)行自動調(diào)整,,可以將這些控件的大小和ScrollView的bounds和contentSize進(jìn)行對比,,意思有點類似,只不過UILabel,UIButton這些控件并不像Scrollview一樣可以在bounds不等于contentSize的情況下進(jìn)行滾動查看內(nèi)容,。
在這里為了使用UILabel的內(nèi)建大小,,我們要保持compression resistance 和 hugging constraints 的垂直方向優(yōu)先級沒有被更高的優(yōu)先級所覆蓋,比如更改了UILabel內(nèi)建大小的優(yōu)先級(priority),并設(shè)置了UILabel的高度約束的優(yōu)先級高于內(nèi)建大小的優(yōu)先級,,那內(nèi)建大小自然就不起作用了,,就會以高優(yōu)先級為準(zhǔn).
- 下面是官方關(guān)于intrinsic content size的說明:
Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.
Editing Auto Layout Constraints
一方面我們確保了AppointmentedInfoCell中的控件,目前全是UILabel,,其內(nèi)建大小垂直方向優(yōu)先級為最高的1000,。
光這個還不夠,我們還要確保內(nèi)建大小的邊緣跟隨內(nèi)建大小一起變化,,從而保證我們的內(nèi)建大小可以起作用,,說白了,就是要求contentView中的子控件建立與superView的約束,,我們先建立第一個UILabel(姓名,、電話)與superview top 的間距約束,然后依次往下建立控件之間推薦間距的約束,,左邊同列控件建立左部對齊約束,,右邊同行內(nèi)容的建立頂部對齊約束,垂直方向的間距約束,,最底部的”預(yù)約結(jié)果Label”建立與superview bottom的間距約束,。
特別提醒:與contentView的四邊間距約束很重要,有了4個與contentView的邊緣約束,,才能保證contentView的大小跟隨其subviews變化,。
上面這些就是建立合理的約束條件,這里隨便提醒一下,,UILabel在IB中布局的大小如果跟內(nèi)容計算出來的不一致就會有警告,比如UILabel長度為200,,目前的內(nèi)容為2個14號字的長度,,那UILabel就會有警告,對于這種警告,,你需要忍住你的強迫癥,。
二、計算行高
這一步在iOS7與iOS8上面就有所不同了,,我們先來看最新的iOS8,。
1、iOS8:
從iOS8開始引入了UITableViewCell的高度的自適應(yīng)功能,,在iOS8之前實現(xiàn)很麻煩的功能,,iOS8以后就不需要自己動手去做了,,稍后我們會看一下iOS7下面如何做。
//打開tableview的高度估算功能
_iTableView.rowHeight = UITableViewAutomaticDimension;
_iTableView.estimatedRowHeight = 70.0;
estimatedRowHeight必須設(shè)置為大于0,,為了畫面的過度順暢,,保證UITableview的高度變化不至于導(dǎo)致UITableview的大范圍滾動而影響了用戶視覺體驗,我們用一個預(yù)估的平均值,,這也就是所謂的預(yù)估值,。estimatedRowHeight并不是最終的行高,當(dāng)一個Cell需要顯示的時候,,會精確計算實際的行高,,contentView的寬度就是UITableview的寬度減去Section Index,accessoryView等的寬度,,contentView的高度則會自動根據(jù)其子視圖的約束關(guān)系計算,,當(dāng)此精確值被計算出過后,estimatedRowHeight,、tableview的contentSize,bounds,這些都會跟隨更新,。
estimatedRowHeight每一行都會使用此值用于該行的預(yù)估值,進(jìn)而初步預(yù)估UITableView的contentSize,。如果UITableView每行內(nèi)容變化很大,,行高差別很大,那我們可以使用以下方法為每一行設(shè)置各不相同的預(yù)估值,。也就是說有通用值,,也有個性值。
- (void)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;
OK,預(yù)估值只是一個插曲,,我們來看精確值,,在iOS8過后,我們只需簡單的激活預(yù)估值,,并在heightForRowAtIndexPath中返回UITableViewAutomaticDimension即可,。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (IS_IOS8_OR_ABOVE) {
return UITableViewAutomaticDimension;
}
}
2、iOS7:
UITableViewCell的contentView的高度自適應(yīng)是iOS8中加入的,,iOS7就只能自己計算了,,所以我們來看一下iOS7下如何處理的。
//NIB注冊,,獲取自定義UITableView實例的方式有很多種,,這里隨便用一種
UINib *cellNib = [UINib nibWithNibName:@"AppointmentedInfoCell" bundle:nil];
[self.tableView registerNib:cellNib forCellReuseIdentifier:@"AppointmentedInfoCell"];
//先創(chuàng)建一個基本的Cell實例,我們后面cellForRowAtIndexPath 和 heightForRowAtIndexPath 都需要用,由于UITableView的加載過程是先計算出所有的行高,,再對每行進(jìn)行渲染的,,即 heightForRowAtIndexPath是先調(diào)用的,所以這里的baseCell就是一個離屏控件,,用于輔助計算高度的,,而后面也可以直接使用其用于每行內(nèi)容的更新,,每種類型的Cell只需要一個即可,這樣我們的離屏內(nèi)存并不會浪費,。
_baseCell = [cellNib instantiateWithOwner:nil options:nil][0];
//更新Cell內(nèi)容
- (void)configureCell:(AppointmentedInfoCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
//更新contentView的子控件
}
//
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
AppointmentedInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppointmentedInfoCell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
//
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
[self configureCell:_baseCell atIndexPath:indexPath];
[_baseCell layoutSubviews];
CGFloat height = [_baseCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height + 1;//由于分割線,,所以contentView的高度要小于row 一個像素。
}
寫本文時,,為了能夠提供一個全面的解決方案不至于太局限,,以免誤導(dǎo)讀者,我翻閱了很多文章,,基本思想都是以上這樣,,區(qū)別在于細(xì)節(jié)的處理上,比如UItableViewCell的實例化就有很多種方式比如:
static NSString *CellIdentifier = @"AppointmentedInfoCell";
AppointmentedInfoCell *cell = (AppointmentedInfoCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
NSArray *cellTeam = [[NSBundle mainBundle] loadNibNamed:CellIdentifier
owner:self
options:nil];
cell = [cellTeam objectAtIndex:0];
}
也有的網(wǎng)友巧妙的使用了dispatch_once:
static AppointmentedInfoCell *baseCell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
baseCell = [[AppointmentedInfoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyTableViewCellIdentifier];
});
OK,到這里,,我們基本上就掌握了AutoLayout的UITableViewCell的動態(tài)高度的處理,,關(guān)鍵點就是systemLayoutSizeFittingSize的使用,希望本篇能夠幫助你更好的理解AutoLayout以及UITableView在iOS8新增的布局處理,。
參考:
本文主要用于一個知識的歸納總結(jié),,過程中可能會引用到其它地方的文字或代碼,如有侵權(quán)請及時聯(lián)系我,,在此對寫作過程中參考了的文章作者表示感謝,!
- http://blog./blog/2013/12/07/auto-layout-and-uitableview-cells/
- 對應(yīng)demo
- http:///blog/2014/02/14/table-view-cells-with-varying-row-heights.html
- 對應(yīng)demo
- http://www./dev/2014/02/leveraging-auto-layout-for-dynamic-cell-heights/
- 對應(yīng)demo
- http:///questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
- 中文翻譯版
- PureLayout
- http://www./73602/dynamic-table-view-cell-height-auto-layout
- https://github.com/Alex311/TableCellWithAutoLayout
- http://www./blog/2014/02/21/dong-tai-ji-suan-uitableviewcellgao-du-xiang-jie/
- http://blog./post/66085151655/using-auto-layout-to-calculate-table-cell-height
- http://www./dynamic-uitableview-cell-heights-programmatically/
- http://www.cnblogs.com/gatsbywang/p/4216706.html
|