1. 程式人生 > >使用Autolayout xib實現動態高度的TableViewCell

使用Autolayout xib實現動態高度的TableViewCell

前言

最近又要做新功能了,雖然沒有什麼難點,只是獲取後端XML資料顯示到TableView,但是不是可以更簡單快速的完成呢?原來Cell的動態高度一直都是通過sizeWithFont手動計算,潛意識覺得這應該不是最好的實現方式,但由於當時時間緊不允許嘗試新技術,所以問題也就遺留了下來,這次又遇到了,時間充裕就解決下吧。

Autolayout是解決自適應frame問題的解決方案(iOS6.0就已經支援了,我現在才用= =#)。通過給檢視元素設定合適的約束條件,內部會根據元素內容和限制條件計算出合適的尺寸顯示。我們就不用自己手動寫這些程式碼了。

文章步驟看上去有些複雜,真正做起來還是很快的,滑鼠拖拽幾下就能完成原來的手動計算高度部分程式碼,減少了很多重複性工作,也讓程式碼看起來更加整潔。

建立Xib檔案

首先將Cell做好佈局,調整到滿意的位置和寬度,然後開始做Autolayout設定。

Autolayout操作方式有兩種,一種是選擇目標後,使用右下角的工具欄;另一種是直接使用右鍵拖拽目標,在彈出的選單中選擇限制項。當選擇的目標比較小的時候,可以開啟左側的選單,在這裡做拖拽操作一樣是可以的。個人感覺後者更方便一些。

開始之前,先來介紹下使用的基本工具吧。

第一個按鈕是和對齊有關的,就是控制多個元素(Lable, Button等)的統一約束。例如我們需要讓標題和內容按照左,就選擇標題和內容元素,選擇Leading Edges設定為5即可。

Autolayout_Align

第二個按鈕是和元素位置固定有關的限制條件,直接看圖吧:

Autolayout_Pin

右側能夠看到當前選擇元素限制條件的列表:

有時候想要一個元素的間距是一個動態值,例如距離右側至少10pt(即>=10pt),那麼可以在上圖中點選右側按鈕(齒輪)進入詳細設定:

Autolayout_Constraint_Relation Config

第三個按鈕是有關清除限制條件、根據限制更新檢視大小的工具。個人比較常用的是清除限制條件,有時候設定錯了很麻煩,直接清除掉重新來就行了。

Autolayout_Resolve Auto Layout Issues

上面這些就是常用到的一些限制條件了。個人覺得使用右鍵拖拽彈出的選單選擇更方便和直觀一些,因為選單中會根據拖拽內容動態顯示可用項供我們選擇,選單如圖

Autolayout_ShortAction

大致就是這些了吧……

我來談談自己的用法。總體上是從上到下,從左到右做約束限制。在這個例子中,就是設定標題->內容->發帖人這樣的順序。

Autolayout_Example

  1. 設定標題的頂部和左側距離,以及寬度(防止超出邊界)。
  2. 設定內容的頂部(距離標題)和左側距離,以及寬度。設定最大行數。
  3. 設定發帖人的頂部和左側距離,以及高度。
  4. 設定發帖時間的頂部和左側距離,距離右側間距(防止內容過長)。
  5. 關鍵步驟,設定發帖人距離底部距離,如果不設定這個引數,那麼下面程式碼計算的Cell高度會永遠是0。

多試一試,如果有錯誤或者缺少限制,XCode會有提示。它報出的錯誤一般都是必須修正的,但它給的自動修正建議有時並不是我們想要的(正確的),想清楚再新增。

程式碼部分

使用了xib製作的Cell,那麼在原來的專案程式碼中如何使用呢?看程式碼:

staticNSString*CellIdentifier=@"CellIdentifier";-(void)viewDidLoad
{//註冊TableView中用於複用的Cell[self.tableView registerNib:[UINib nibWithNibName:@"BBSPostContentCell" bundle:nil] forCellReuseIdentifier:CellIdentifier];//...}//關鍵方法,獲取複用的Cell後模擬賦值,然後取得Cell高度-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{BBSPostContentCell*cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];NSDictionary*dataSourceItem =[self.dataSource objectAtIndex:indexPath.row];
    cell.titleLabel.text =[dataSourceItem valueForKey:@"title"];
    cell.contentLabel.text =[dataSourceItem valueForKey:@"body"];[cell setNeedsUpdateConstraints];[cell updateConstraintsIfNeeded];CGFloat height =[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;return height;}//在cellForRowAtIndexPath中,按照常規方法做賦值就行了-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{BBSPostContentCell*cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];NSDictionary*dic = dataSource[indexPath.row];
    cell.titleLabel.text = dic[@"title"];
    cell.contentLabel.text = dic[@"body"];return cell;}

2014.1.2: 在測試時發現這部分的程式碼還存在一些效能問題(整個表檢視在更新時會卡頓),我會稍後補上。

我在使用Instruments分析發現,heightForRowAtIndexPath中呼叫dequeueReusableCellWithIdentifier會佔用很多CPU資源,因此我試著不使用registerNib方法註冊複用Cell,而在程式碼中手動處理,類似這樣:

BBSPostContentCell*cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];if(cell ==nil){
    cell =[[NSBundle mainBundle] loadNibNamed:@"BBSPostContentCell" owner:self options:NULL][0];NSLog(@"cell loadNibNamed");}else{NSLog(@"cell dequeueReusableCellWithIdentifier");}

這時我發現這裡的Cell呼叫dequeueReusableCellWithIdentifier方法總是返回nil,因此每次都是從xib中載入,從而耗費了大量的資源。問題的原因我還不清楚,目前我的解決方法是,單獨生成一個Cell用於在heightForRowAtIndexPath方法中計算高度。

其次,在[tableView reloadData]和[tableView insertRowsAtIndexPaths]時,底層會將所有行高重新計算,這個會佔用大量的時間,因此我試著對行高做了快取,暫時解決了這個問題。

關於相容性問題

由於Autolayout只能在iOS6.0以上版本使用,而根據友盟統計,目前6.0以下的使用者大概還有8%左右(2013.12)。現在有兩個辦法解決:

  1. 哥不在乎,放棄這些使用者!(好霸氣=。=)把專案的部署版本修改為6.0以上即可。
  2. 咳…咳…這個嘛,使用者還是有必要支援的………恩,那我們來說說這個怎麼相容。

思路很簡單,我們告訴XCode,6.0以上版本使用Autolayout,以下的舊版本不要使用這個就可以了。

將原xib檔案inspector中選擇”Interface Builder Document”->”Build for”->”iOS 6.0 and Later”,告訴XCode,這個xib在6.0以上裝置編譯。

將xib檔案拷貝一份副本,命名為”xxx_iOS5.xib”,在inspector中選擇”Project Deployment Target”,也就是說使用專案部署目標版本(即最低版本5.0),並取消”Use Autolayout”選項。

在程式碼中根據系統版本載入不同的xib檔案:

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \
([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch]!=NSOrderedAscending)#define IS_SUPPORT_AUTOLAYOUT   SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")-(void)viewDidLoad
{if(!IS_SUPPORT_AUTOLAYOUT){//for iOS 5.x[self.tableView registerNib:[UINib nibWithNibName:@"BBSPostContentCell_iOS5" bundle:nil] forCellReuseIdentifier:CellIdentifier];}else{[self.tableView registerNib:[UINib nibWithNibName:@"BBSPostContentCell" bundle:nil] forCellReuseIdentifier:CellIdentifier];}}

最後別忘了在高度計算時,區分下程式碼:

-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{if(IS_SUPPORT_AUTOLAYOUT){//Autolayout部分程式碼,同上//.....return height;}else{//for iOS 5.x//為了簡單起見,就直接使用固定值了,當然如果你要自己為iOS5使用者手動計算動態高度,也是可以的。return81;}}