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

摘要 前言 最近又要作新功能了,雖然沒有什麼難點,只是獲取後端XML數據顯示到TableView,可是不是能夠更簡單快速的完成呢?原來Cell的動態高度一直都是經過sizeWithFont手動計算,潛意識以爲這應該不是最好的實現方式,但因爲當時時間緊不容許嘗試新技術,因此問題也就遺留了下來,此次又遇到了,時間充裕就解決下吧。 Autolayout是解決自適應frame問題的解決方案(iOS6.0就已經支持了,我如今才用= =#)。經過給視圖元素設置合適的約束條件,內部會根據元素內容和限制條件計算出合適的尺寸顯示。咱們就不用本身手動寫這些代碼了。 文章步驟看上去有些複雜,真正作起來仍是很快...後端

iOS

 

 
 
 
 

建立Xib文件

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

Autolayout操做方式有兩種,一種是選擇目標後,使用右下角的工具欄;另外一種是直接使用右鍵拖拽目標,在彈出的菜單中選擇限制項。當選擇的目標比較小的時候,能夠打開左側的菜單,在這裏作拖拽操做同樣是能夠的。我的感受後者更方便一些。佈局

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

 

第一個按鈕是和對齊有關的,就是控制多個元素(Lable, Button等)的統一約束。例如咱們須要讓標題和內容按照左,就選擇標題和內容元素,選擇Leading Edges設置爲5便可。測試

Autolayout_Align

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

Autolayout_Pin

右側可以看到當前選擇元素限制條件的列表:spa

Autolayout_Inspector_Constrainsts list
這裏有兩個參數,「Content Hugging Priority」和「Content Compression Resistance Priority」,感受不太好理解,棧爆上找到一篇解釋,講的挺好的:Cocoa Autolayout: content hugging vs content compression resistance priority.net

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

Autolayout_Constraint_Relation Config

第三個按鈕是有關清除限制條件、根據限制更新視圖大小的工具。我的比較經常使用的是清除限制條件,有時候設置錯了很麻煩,直接清除掉從新來就好了。

Autolayout_Resolve Auto Layout Issues

上面這些就是經常使用到的一些限制條件了。我的以爲使用右鍵拖拽彈出的菜單選擇更方便和直觀一些,由於菜單中會根據拖拽內容動態顯示可用項供咱們選擇,菜單如圖

Autolayout_ShortAction

大體就是這些了吧……

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

Autolayout_Example

  1. 設置標題的頂部和左側距離,以及寬度(防止超出邊界)。

  2. 設置內容的頂部(距離標題)和左側距離,以及寬度。設置最大行數。

  3. 設置發帖人的頂部和左側距離,以及高度。

  4. 設置發帖時間的頂部和左側距離,距離右側間距(防止內容過長)。

  5. 關鍵步驟,設置發帖人距離底部距離,若是不設置這個參數,那麼下面代碼計算的Cell高度會永遠是0。

多試一試,若是有錯誤或者缺乏限制,XCode會有提示。它報出的錯誤通常都是必須修正的,但它給的自動修正建議有時並非咱們想要的(正確的),想清楚再添加。

代碼部分

使用了xib製做的Cell,那麼在原來的項目代碼中如何使用呢?看代碼:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

static NSString *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,而在代碼中手動處理,相似這樣:

 

1

2

3

4

5

6

7

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文件:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#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];

}

}

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

 

1

2

3

4

5

6

7

8

9

10

11

12

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

{

    if (IS_SUPPORT_AUTOLAYOUT) {

        //Autolayout部分代碼,同上

        //.....

        return height;

    } else {

     //for iOS 5.x

     //爲了簡單起見,就直接使用固定值了,固然若是你要本身爲iOS5用戶手動計算動態高度,也是能夠的。

        return 81;

    }

}

原文連接: http://my.oschina.net/u/2360693/blog/481236?p={{totalPage}}
相關文章
相關標籤/搜索