四種方法實(shí)現(xiàn)UITableView的cell高度自動(dòng)計(jì)算

UITableview是iOS開發(fā)中使用最頻繁的一個(gè)控件,在實(shí)際開發(fā)中,我們經(jīng)常需要定制cell,讓cell顯示圖片、文字等。由于cell包含的圖片和文字是根據(jù)服務(wù)器返回的數(shù)據(jù)進(jìn)行填充的,這就導(dǎo)致cell包含的內(nèi)容的高度是不定的。

四種方法計(jì)算cell的高度:

1、iOS8的自動(dòng)計(jì)算機(jī)制,需要autolayout(適用iOS8之后系統(tǒng))
2、iOS6之后系統(tǒng)API結(jié)合autolayout進(jìn)行計(jì)算(適用于iOS6之后的系統(tǒng))
3、手動(dòng)計(jì)算(適用于iOS6之后的系統(tǒng))
4、借助于第三方框架自動(dòng)計(jì)算(適用于iOS6之后的系統(tǒng))


方法1:iOS8的自動(dòng)計(jì)算

此方法必須使用autolayout,這里我是用的xib設(shè)置的,也可以使用第三方框架masonry設(shè)置。

設(shè)置約束的時(shí)候必須注意每個(gè)控件在垂直方向上必須都有約束,這樣cell才可以計(jì)算出來(lái)高度。
下面我們來(lái)看看cell的內(nèi)部控件的垂直方向的約束如何設(shè)置

昵稱label的垂直約束
Paste_Image.png
內(nèi)容文字label的垂直約束
Paste_Image.png
內(nèi)容圖片的垂直約束

height <= 400這個(gè)約束也可以不用設(shè)置,我這里是為了不讓圖片過(guò)長(zhǎng),所以限制了高度。

Paste_Image.png

下面我只貼出計(jì)算高度的代碼,整個(gè)Demo我會(huì)放大github上面。

具體實(shí)現(xiàn)代碼

iOS8時(shí)代的高度計(jì)算非常簡(jiǎn)單,下面兩行代碼就搞定了,非常方便。
前提是需要設(shè)置好在垂直高度上的約束。

- (void)viewDidLoad{    
    self.tableView.estimatedRowHeight = 80.0f;
    self.tableView.rowHeight = UITableViewAutomaticDimension;
}

效果如下

方法2:iOS6的系統(tǒng)API結(jié)合autolayout

控件的約束和第一個(gè)方法的一樣,下面列出的代碼是和第一個(gè)方法不同的地方。
該方法的demo和第一個(gè)方法的demo是同一個(gè),每個(gè)方法獨(dú)立使用到的代碼我會(huì)特別注明,沒(méi)有注明就是所有方法共有的。

//TableViewCell.m文件
//======================
- (void)setModel:(TableViewModel *)model
{
    //必須設(shè)置label的最大寬度,不然系統(tǒng)無(wú)法計(jì)算label的最大高度
    CGFloat preferredWidth = [UIScreen mainScreen].bounds.size.width - 53;
    self.userName.preferredMaxLayoutWidth = preferredWidth;
    self.userContentString.preferredMaxLayoutWidth = preferredWidth;

    self.headImage.image = model.userHeadImage;
    self.userContentImage.image = model.userContentImage;
    self.userContentString.text = model.userContentString;
    self.userName.text = model.userName;
}
//viewController.m文件
//===========================
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    static TableViewCell *Cell;
    static dispatch_once_t onceToken;
    //必須使用dispatch_once,保證只會(huì)從緩存池中取一個(gè)cell用于高度計(jì)算,其他的cell高度都是用這個(gè)cell的高度。不然每次都從緩存池中取出來(lái)不同的cell,導(dǎo)致高度計(jì)算出問(wèn)題
    dispatch_once(&onceToken, ^{
        Cell = [tableView dequeueReusableCellWithIdentifier:CellId];
    });

   TableViewModel *model = self.modelArray[indexPath.row];
    Cell.model = model;
    // 根據(jù)當(dāng)前數(shù)據(jù),計(jì)算Cell的高度,注意+1是contentview和cell之間的分割線高度
    model.cellHeight = [Cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height +1.0f;
   return model.cellHeight;
}

//實(shí)現(xiàn)該方法后,tableview就不會(huì)一次性調(diào)用完所有cell的高度,有些不在可見范圍的cell是不需要一開始就知道高度的。當(dāng)然,estimatedHeightForRowAtIndexPath方法調(diào)用頻率就會(huì)非常高,所以我們盡量返回一個(gè)比較接近實(shí)際結(jié)果的固定值以提高性能.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 112.0f;
}

```
該方法實(shí)現(xiàn)效果和方法一相同

---
###方法3、手動(dòng)計(jì)算
該方法需要手動(dòng)計(jì)算垂直高度上每個(gè)控件的高度,然后相加得出cell的高度。
這種方法最繁瑣,但是也是最精確的,也是最可控的。
使用這個(gè)方法,可以不需要使用autolayout設(shè)置約束,直接使用frame設(shè)置每個(gè)控件的位置。但是為了方便,我這里還是使用autolayout設(shè)置控件的約束和位置。

因?yàn)樾枰_切的知道每個(gè)控件的高度,所以這里image的高度必須是固定的,這樣才可以進(jìn)行cell的高度計(jì)算
修改如下

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/277755-af1882da9e9033c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

######修改的代碼如下:
```
//TableViewModel.m文件
//===============================
#import "TableViewModel.h"

@implementation TableViewModel

//方法3代碼
- (CGFloat)cellHeight{
    // 文字的最大尺寸(設(shè)置內(nèi)容label的最大size,這樣才可以計(jì)算label的實(shí)際高度,需要設(shè)置最大寬度,但是最大高度不需要設(shè)置,只需要設(shè)置為最大浮點(diǎn)值即可),53為內(nèi)容label到cell左邊的距離
    CGSize maxSize = CGSizeMake([UIScreen mainScreen].bounds.size.width - 53, MAXFLOAT);
  
    // 計(jì)算內(nèi)容label的高度
    CGFloat textH = [self.userContentString boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size.height;
    
    /*
     昵稱label和cell的頂部為0
     17為昵稱label的高度
     8.5為昵稱label和內(nèi)容label的間距
     textH為內(nèi)容label的高度
     304為內(nèi)容image的高度
     */
    _cellHeight = 0 + 17 + 8.5 + 8 +textH + 304;
    
    return _cellHeight;
}

```
```
//ViewController.m文件
//==========================

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
 //方法3代碼
   TableViewModel *model =  self.modelArray[indexPath.row];
    return model.cellHeight;
}
```

---
###方法四、使用第三方框架
這是國(guó)內(nèi)的一個(gè)大神寫的框架,可以一行代碼就實(shí)現(xiàn)cell的高度自動(dòng)計(jì)算。同時(shí)還能實(shí)現(xiàn)緩存高度,最低兼容版本為iOS6。
實(shí)現(xiàn)代碼就不寫了,非常簡(jiǎn)單

具體看這篇文章:《[優(yōu)化UITableViewCell高度計(jì)算的那些事](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/)》

這里啰嗦兩點(diǎn),也是自己踩過(guò)的坑:

1、在博主的文字里面提到使用的時(shí)候直接使用如下代碼即可:
```
#import <UITableView+FDTemplateLayoutCell.h>
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {
        // 配置 cell 的數(shù)據(jù)源,和 "cellForRow" 干的事一致,比如:
        cell.entity = self.feedEntities[indexPath.row];
    }];
}
```
一定要把上面的id cell,換成自己的cell類,比如我的就是WSTableViewCell *cell。算是一個(gè)小坑吧。

2、很多人肯定吃過(guò)self-sizing-cell的虧,覺(jué)得我上面都設(shè)置對(duì)了,為什么就是算不出來(lái)高度呢?

要滿足self-sizing-cell,必須滿足兩點(diǎn):

* 你的cell里面的控件必須在上下左右四個(gè)方向都有約束到cell的四個(gè)邊。如下圖:
![](http://upload-images.jianshu.io/upload_images/277755-b542dff0d55ddc91.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

*約束一定要是控件和cell的contentView邊緣之間的約束,而不是控件和cell邊緣的之間的約束。
 
因?yàn)樵O(shè)計(jì)給的圖,cell內(nèi)部的控件和cell的距離是到cell邊緣的距離,然后我就發(fā)現(xiàn)怎么都不能進(jìn)行高度自動(dòng)計(jì)算,所有的cell全部疊在一起了。被這個(gè)坑了很久,一直找不出來(lái)原因。另外cell的邊緣和cell的contentView的邊緣相差8pt。

######錯(cuò)誤設(shè)置:

ReplayCell是cell的名字


![Paste_Image.png](http://upload-images.jianshu.io/upload_images/277755-6c893ca13fbe137c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

######正確設(shè)置:
superView是cell的contentView
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/277755-1a3adac25658e4e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

---
###總結(jié):
上面四種方法各有優(yōu)缺點(diǎn),如果你的App最低兼容版本是iOS8,那請(qǐng)毫不猶豫的選擇方法一的系統(tǒng)方法吧,高效簡(jiǎn)潔。

如果你需要最低兼容iOS6,可以從其他三個(gè)方法中選一個(gè),建議使用方法四的第三方框架,高效強(qiáng)大。

所有的代碼都放在了Github上面,小伙伴們可以參考下。

地址如下:
https://github.com/XiMu-Demo/Blog-Demo/tree/master/calculateCellHeight
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評(píng)論 3 420
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,931評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,533評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,891評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,319評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,794評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,076評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容