我們經常會遇到UITableViewCell的高度要跟隨內容而調整,在未引入AutoLayout之前,我們使用以下方法計算Label高度,然後heightForRowAtIndexPath中返回計算的高度,這種做法,真的很土很局限很不好,如果UILabel使用了CoreText或者UIKit進行了富文本不同字體的排版,它更是沒辦法,我還得分段來計算,總之各種麻煩。
- (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之後,情況是否有所變化呢?當然!而且AutoLayout在iOS不斷更新過程中,也在一起不斷的優化,以方便開發者進行布局。說實話,跟很多開發者一樣,我目前也並不是特別喜歡AutoLayout,有一些不可控的因素,布局並沒有完全掌握在自己手上,需要依賴系統根據約束進行調整,這讓保守的開發人員很沒有安全感。不廢話了,我們還是進入到正題。
我們直接拿一個現實的需求來進行討論,如下圖,我們需要構建一個頁面,上面部分顯示我們預約保養的基本信息,下面部分顯示該門店目前提供的優惠券列表,這種需求最簡單的做法就是直接用兩種UITableViewCell,下面的部分的UITableViewCell要簡單一些,高度固定,而上面部分的UITableViewCell的內容有很多不確定的內容,比如用戶預約保養選擇的項目,門店的名稱地址,這些UILabel的高度都不確定,所以導致上面部分的UITableViewCell的高度需要動態調整,這是一個比較典型的實例,我們一起來看一下如何解決。
一、建立合理的約束
我們先建立自定義Cell: AppointmentedInfoCell (創建XIB)。 然後設置合理的約束條件,什麼是合理的約束條件,一方面我們需要按前面講到的設置正確的約束條件,另一方面我的意思主要是控件的compression resistance 和 hugging constraints ,在IB中如下圖:
我們知道在Autolayout中,我們的UILabel,UIButton等控件都有了內建大小(intrinsic content size),就是說控件的大小會根據內容進行自動調整,可以將這些控件的大小和ScrollView的bounds和contentSize進行對比,意思有點類似,只不過UILabel,UIButton這些控件並不像Scrollview一樣可以在bounds不等於contentSize的情況下進行滾動查看內容。 在這裡為了使用UILabel的內建大小,我們要保持compression resistance 和 hugging constraints 的垂直方向優先級沒有被更高的優先級所覆蓋,比如更改了UILabel內建大小的優先級(priority),並設置了UILabel的高度約束的優先級高於內建大小的優先級,那內建大小自然就不起作用了,就會以高優先級為准.
- 下面是官方關於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,其內建大小垂直方向優先級為最高的1000。 光這個還不夠,我們還要確保內建大小的邊緣跟隨內建大小一起變化,從而保證我們的內建大小可以起作用,說白了,就是要求contentView中的子控件建立與superView的約束,我們先建立第一個UILabel(姓名、電話)與superview top 的間距約束,然後依次往下建立控件之間推薦間距的約束,左邊同列控件建立左部對齊約束,右邊同行內容的建立頂部對齊約束,垂直方向的間距約束,最底部的”預約結果Label”建立與superview bottom的間距約束。
特別提醒:與contentView的四邊間距約束很重要,有了4個與contentView的邊緣約束,才能保證contentView的大小跟隨其subviews變化。
上面這些就是建立合理的約束條件,這裡隨便提醒一下,UILabel在IB中布局的大小如果跟內容計算出來的不一致就會有警告,比如UILabel長度為200,目前的內容為2個14號字的長度,那UILabel就會有警告,對於這種警告,你需要忍住你的強迫症。
二、計算行高
這一步在iOS7與iOS8上面就有所不同了,我們先來看最新的iOS8。
1、iOS8:
從iOS8開始引入了UITableViewCell的高度的自適應功能,在iOS8之前實現很麻煩的功能,iOS8以後就不需要自己動手去做了,稍後我們會看一下iOS7下面如何做。
//打開tableview的高度估算功能 _iTableView.rowHeight = UITableViewAutomaticDimension; _iTableView.estimatedRowHeight = 70.0;
estimatedRowHeight必須設置為大於0,為了畫面的過度順暢,保證UITableview的高度變化不至於導致UITableview的大范圍滾動而影響了用戶視覺體驗,我們用一個預估的平均值,這也就是所謂的預估值。estimatedRowHeight並不是最終的行高,當一個Cell需要顯示的時候,會精確計算實際的行高,contentView的寬度就是UITableview的寬度減去Section Index,accessoryView等的寬度,contentView的高度則會自動根據其子視圖的約束關系計算,當此精確值被計算出過後,estimatedRowHeight、tableview的contentSize,bounds,這些都會跟隨更新。
estimatedRowHeight每一行都會使用此值用於該行的預估值,進而初步預估UITableView的contentSize。如果UITableView每行內容變化很大,行高差別很大,那我們可以使用以下方法為每一行設置各不相同的預估值。也就是說有通用值,也有個性值。
- (void)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;
OK,預估值只是一個插曲,我們來看精確值,在iOS8過後,我們只需簡單的激活預估值,並在heightForRowAtIndexPath中返回UITableViewAutomaticDimension即可。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (IS_IOS8_OR_ABOVE) { return UITableViewAutomaticDimension; } }
2、iOS7:
UITableViewCell的contentView的高度自適應是iOS8中加入的,iOS7就只能自己計算了,所以我們來看一下iOS7下如何處理的。
//NIB注冊,獲取自定義UITableView實例的方式有很多種,這裡隨便用一種 UINib *cellNib = [UINib nibWithNibName:@"AppointmentedInfoCell" bundle:nil]; [self.tableView registerNib:cellNib forCellReuseIdentifier:@"AppointmentedInfoCell"]; //先創建一個基本的Cell實例,我們後面cellForRowAtIndexPath 和 heightForRowAtIndexPath 都需要用,由於UITableView的加載過程是先計算出所有的行高,再對每行進行渲染的,即 heightForRowAtIndexPath是先調用的,所以這裡的baseCell就是一個離屏控件,用於輔助計算高度的,而後面也可以直接使用其用於每行內容的更新,每種類型的Cell只需要一個即可,這樣我們的離屏內存並不會浪費。 _baseCell = [cellNib instantiateWithOwner:nil options:nil][0]; //更新Cell內容 - (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 一個像素。 }
寫本文時,為了能夠提供一個全面的解決方案不至於太局限,以免誤導讀者,我翻閱了很多文章,基本思想都是以上這樣,區別在於細節的處理上,比如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]; }
也有的網友巧妙的使用了dispatch_once:
static AppointmentedInfoCell *baseCell; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ baseCell = [[AppointmentedInfoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyTableViewCellIdentifier]; });
OK,到這裡,我們基本上就掌握了AutoLayout的UITableViewCell的動態高度的處理,關鍵點就是systemLayoutSizeFittingSize的使用,希望本篇能夠幫助你更好的理解AutoLayout以及UITableView在iOS8新增的布局處理。
參考:
本文主要用於一個知識的歸納總結,過程中可能會引用到其它地方的文字或代碼,如有侵權請及時聯系我,在此對寫作過程中參考了的文章作者表示感謝!
- http://blog.jldagon.me/blog/2013/12/07/auto-layout-and-uitableview-cells/
- 對應demo
- http://useyourloaf.com/blog/2014/02/14/table-view-cells-with-varying-row-heights.html
- 對應demo
- http://www.raizlabs.com/dev/2014/02/leveraging-auto-layout-for-dynamic-cell-heights/
- 對應demo
- http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
- 中文翻譯版
- PureLayout
- http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout
- https://github.com/Alex311/TableCellWithAutoLayout
- http://www.ifun.cc/blog/2014/02/21/dong-tai-ji-suan-uitableviewcellgao-du-xiang-jie/
- http://blog.amyworrall.com/post/66085151655/using-auto-layout-to-calculate-table-cell-height
- http://www.macspotsblog.com/dynamic-uitableview-cell-heights-programmatically/
- http://www.cnblogs.com/gatsbywang/p/4216706.html