UITableViewCell高度自適應探索--cell預估高度(二)

有了預估高度這個先決條件,一切都好說了.我們直接從代碼入手.
接下來我們實現一個簡單的信息展示功能,如:


Demo最終效果

每個cell里面可能只有圖或者只有文字,更多的情況是圖文并茂,但是文字的長短也是不一樣的.

創建項目和展示輸入的過程就不說了,這里只講幾個主要的部分:


  • 1.最主要的當然是在我們控制器內部加上前面講的協議方法
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 55.f;
}

注意這里的預估高度當然是越接近越好,但其實還是比較隨意,即使和真實高度差大一點也沒有關系.但是還是不要寫得太小吧.

  • 2.自定義cell,這里使用的是xib


    cell內部控件的約束

顯示文字的label,一開始應該都會想到上下左右間距,于是這里我們暫時給label上、左、右都距離父控件為10的間距(后面會調整),然后下面距離imageView的間距也是10,imageView左邊和label左邊對齊,然后寬高固定.

接著把兩個控件連線到cell的.m文件中:


xib拖出的屬性
  • 3.繪制cell的時候,一般情況下控制器會向cell傳遞一個數據模型,讓cell負責數據的顯示.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MessageCell"];
    cell.message = self.dataList[indexPath.row];
    return cell;
}

代碼中self.dataList是存放所有消息模型的數組.

  • 4.來到MessageCell.m文件中,手動實現模型的setter方法:
- (void)setMessage:(Message *)message {
    _message = message;
    self.contentLabel.text = _message.content;
    self.contentImageView.image = [UIImage imageNamed:_message.imageName];
}

到此,我們就完成了cell內容的基本展示.由于高度我們還沒開始適應,暫時給了一個固定的150的高度,先看下效果:


Snip20150608_18.png

數據的展示是沒問題了,我們開始進行關鍵的一步,自適應.

  • 5.還是循著最早的思路,我們希望在繪制cell的時候拿到cell的高度.

比較好的方法是:cell在拿到數據模型并展示后,我們就可以得到cell準確的高度,這時候把它存放在數據模型里面.(放到數據模型里面的好處是:tableView在需要cell高度的時候就可以直接從數據模型里面取.)

所以我們的數據模型除了文字和圖片,需要再添加一個屬性,模型的頭文件如下:

#import <UIKit/UIKit.h>
@interface Message : NSObject
@property (nonatomic, copy) NSString *imageName;
@property (nonatomic, copy) NSString *content;
@property (nonatomic, assign) CGFloat cellHeight;

+ (instancetype)messageWithDic:(NSDictionary *)dic;
@end

tips:由于模型直接繼承自NSObject,創建的時候只包含了Fundation框架,所以添加CGFloat類型的屬性的時候會報錯,這時候只要把fundation改成UIKit就可以了(UIKit內部也包含了Fundation).

接下來我們就可以計算cellHeight的值了,還是在cell的模型setter方法里面:

- (void)setMessage:(Message *)message {
    _message = message;
    self.contentLabel.text = _message.content;
    self.contentImageView.image = [UIImage imageNamed:_message.imageName];
    // 獲取imageView底部的frame再加上一些間距作為行高
    self.message.cellHeight = CGRectGetMaxY(self.contentImageView.frame) + 10;
}

同時,在控制器heightForRow...協議方法里面寫上:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    Message *message = self.dataList[indexPath.row];
    return message.cellHeight;
}

一切看起來是那么的天衣無縫,接下來是見證奇跡的時刻:


效果0.7

WTF?說好的自適應呢?

其實問題出現在這里:

- (void)setMessage:(Message *)message {
    _message = message;
    self.contentLabel.text = _message.content;
    self.contentImageView.image = [UIImage imageNamed:_message.imageName];
    self.message.cellHeight = CGRectGetMaxY(self.contentImageView.frame) + 10;
}

我們在得到cellHeight的時候,直接是取imageView的底部+10作為行高,但是在這句之前,label和imageView剛剛拿到數據,還沒開始布局,所以我們要在獲取cellHeight之前調用layoutIfNeeded方法把他們強制布局一下. 升級后的代碼:

- (void)setMessage:(Message *)message {
    _message = message;
    // 有的模型不存在文字,這里判斷一下
    if (_message.content.length) {
        self.contentLabel.text = _message.content;
    }
    else {
        self.contentLabel.text = nil;
    }

    // 有的模型不存在圖片,這里進行一下判斷
    if (_message.imageName.length) {
        self.contentImageView.image = [UIImage imageNamed:_message.imageName];
    }
    else {
        self.contentImageView.image = nil;
    }
    // 強制布局
    [self layoutIfNeeded];
    self.message.cellHeight = CGRectGetMaxY(self.contentImageView.frame) + 10;
}

再運行看看效果:


效果0.8

好像有那么點意思了,起碼對于文字和圖片齊全的模型已經可以了.然后我們處理那些特殊的情況.

還是那個setter方法里面,我們對image的有無進行判讀,如果沒有圖片,我們直接取label的底邊(加點間距)作為cellHeight,代碼如下:

- (void)setMessage:(Message *)message {
    _message = message;

    if (_message.content.length) {
        self.contentLabel.text = _message.content;
    }
    else {
        self.contentLabel.text = nil;
    }

    [self layoutIfNeeded];

    if (_message.imageName.length) {
        self.contentImageView.image = [UIImage imageNamed:_message.imageName];
        self.message.cellHeight = CGRectGetMaxY(self.contentImageView.frame) + 10;
    }
    else {
        self.contentImageView.image = nil;
        self.message.cellHeight = CGRectGetMaxY(self.contentLabel.frame) + 10;
    }
}

再看效果:


效果0.9

好很多了.但是還有一些細節的問題,比如:


沒有完全適應的cell

這行沒有圖片的cell,我們設置行高是label底部加10,但一看這個距離明顯是大于10了.當把這行cell滑出屏幕再滑回來,又恢復正常.

這個其實是label的問題.
目前我們在label身上設置的和寬度有關的約束是左右距離父控件各為10,但這種約束算出來的label的高度有時候會不準,所以我們需要給label再設定一個屬性:

在cell的awakeFromNib:方法里面:

- (void)awakeFromNib {
    self.contentLabel.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20;
}

這個屬性表示設置lable文字的最大寬度,是專門為多行label準備的,使用這個屬性可以準確算出label的高度.ps:設置了這個屬性后,label右邊的約束可以省略不寫,label仍然可以換行顯示.

完成90%了,還剩最后一個問題:


只有圖片的cell

在只有圖片沒有文字的cell中,圖片距離頂部的高度比我們期望的(10)略高(其實是20),因為這時候沒有文字,所以label的高度自動變為0,但是label頂部距離cell上邊還有10,label底部距離imageView還有10,加起來就是20的距離.

這個問題我們可以這樣解決:當沒有文字的時候,我們調整label距離頂部的約束為0,有文字的時候再變回10.所以需要把表示label距離cell頂部的約束從xib中拖出來.

然后在setter方法中分別進行判斷和設置:

if (_message.content.length) {
    self.contentLabel.text = _message.content;
    // 有文字的時候距離頂部是10
    self.labelTopConstraint.constant = 10;
}
else {
    self.contentLabel.text = nil;
    // 沒文字的時候距離頂部為0
    self.labelTopConstraint.constant = 0;
 }
大功告成啦!

是不是發現使用AutoLayout后cell自適應的高度比設置frame時代簡單了不是一點半點.

但是,雖然用起來爽,這種方式也是有缺陷的:

1.由于cell在estimatedHeightForRow...方法中拿到的只是估計的高度,滑動屏幕的時候,tableView不斷拿到真實的高度對contentSize及滾動條的大小等重新計算,由于實際值和預估值的偏差,可能導致滾動條大小不穩定甚至明顯跳動.

2.另外,如果使用的estimatedHeightForRow...方法后,如果你想滾動到最后一行(比如聊天功能,可能在鍵盤彈上去后tableView滾到底部),也會計算不準.因為開啟估算高度胡,cell出現在屏幕上才會返回真實高度,如果根據indexPath直接跳轉到最后一行,后面的cell沒有出現在屏幕上過,依然是根據估算高度來算的,所以會導致滾動的位置不準確.

不過呢,如果對這方面要求不是特別高,一般的需求是可以滿足了.

demo地址:https://github.com/CoderAO/AutoCellHeightWithAutolayout

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容