UITableViewCell高度自適應(yīng)探索--cell預(yù)估高度

有了預(yù)估高度這個(gè)先決條件,一切都好說了.我們直接從代碼入手.

接下來我們實(shí)現(xiàn)一個(gè)簡單的信息展示功能,如:

Demo最終效果

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

創(chuàng)建項(xiàng)目和展示輸入的過程就不說了,這里只講幾個(gè)主要的部分:

1.最主要的當(dāng)然是在我們控制器內(nèi)部加上前面講的協(xié)議方法

- (CGFloat)tableView:(UITableView*)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath*)indexPath {return55.f;}

注意這里的預(yù)估高度當(dāng)然是越接近越好,但其實(shí)還是比較隨意,即使和真實(shí)高度差大一點(diǎn)也沒有關(guān)系.但是還是不要寫得太小吧.

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

cell內(nèi)部控件的約束

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

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

xib拖出的屬性

3.繪制cell的時(shí)候,一般情況下控制器會(huì)向cell傳遞一個(gè)數(shù)據(jù)模型,讓cell負(fù)責(zé)數(shù)據(jù)的顯示.

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {? ? MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MessageCell"];? ? cell.message =self.dataList[indexPath.row];returncell;}

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

4.來到MessageCell.m文件中,手動(dòng)實(shí)現(xiàn)模型的setter方法:

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

到此,我們就完成了cell內(nèi)容的基本展示.由于高度我們還沒開始適應(yīng),暫時(shí)給了一個(gè)固定的150的高度,先看下效果:

Snip20150608_18.png

數(shù)據(jù)的展示是沒問題了,我們開始進(jìn)行關(guān)鍵的一步,自適應(yīng).

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

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

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

#import@interfaceMessage:NSObject@property(nonatomic,copy)NSString*imageName;@property(nonatomic,copy)NSString*content;@property(nonatomic,assign)CGFloatcellHeight;+ (instancetype)messageWithDic:(NSDictionary*)dic;@end

tips:由于模型直接繼承自NSObject,創(chuàng)建的時(shí)候只包含了Fundation框架,所以添加CGFloat類型的屬性的時(shí)候會(huì)報(bào)錯(cuò),這時(shí)候只要把fundation改成UIKit就可以了(UIKit內(nèi)部也包含了Fundation).

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

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

同時(shí),在控制器heightForRow...協(xié)議方法里面寫上:

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

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

效果0.7

WTF?說好的自適應(yīng)呢?

其實(shí)問題出現(xiàn)在這里:

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

我們?cè)诘玫絚ellHeight的時(shí)候,直接是取imageView的底部+10作為行高,但是在這句之前,label和imageView剛剛拿到數(shù)據(jù),還沒開始布局,所以我們要在獲取cellHeight之前調(diào)用layoutIfNeeded方法把他們強(qiáng)制布局一下. 升級(jí)后的代碼:

- (void)setMessage:(Message *)message {? ? _message = message;// 有的模型不存在文字,這里判斷一下if(_message.content.length) {self.contentLabel.text = _message.content;? ? }else{self.contentLabel.text =nil;? ? }// 有的模型不存在圖片,這里進(jìn)行一下判斷if(_message.imageName.length) {self.contentImageView.image = [UIImageimageNamed:_message.imageName];? ? }else{self.contentImageView.image =nil;? ? }// 強(qiáng)制布局[selflayoutIfNeeded];self.message.cellHeight =CGRectGetMaxY(self.contentImageView.frame) +10;}

再運(yùn)行看看效果:

效果0.8

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

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

- (void)setMessage:(Message *)message {? ? _message = message;if(_message.content.length) {self.contentLabel.text = _message.content;? ? }else{self.contentLabel.text =nil;? ? }? ? [selflayoutIfNeeded];if(_message.imageName.length) {self.contentImageView.image = [UIImageimageNamed:_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

好很多了.但是還有一些細(xì)節(jié)的問題,比如:

沒有完全適應(yīng)的cell

這行沒有圖片的cell,我們?cè)O(shè)置行高是label底部加10,但一看這個(gè)距離明顯是大于10了.當(dāng)把這行cell滑出屏幕再滑回來,又恢復(fù)正常.

這個(gè)其實(shí)是label的問題.

目前我們?cè)趌abel身上設(shè)置的和寬度有關(guān)的約束是左右距離父控件各為10,但這種約束算出來的label的高度有時(shí)候會(huì)不準(zhǔn),所以我們需要給label再設(shè)定一個(gè)屬性:

在cell的awakeFromNib:方法里面:

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

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

完成90%了,還剩最后一個(gè)問題:

只有圖片的cell

在只有圖片沒有文字的cell中,圖片距離頂部的高度比我們期望的(10)略高(其實(shí)是20),因?yàn)檫@時(shí)候沒有文字,所以label的高度自動(dòng)變?yōu)?,但是label頂部距離cell上邊還有10,label底部距離imageView還有10,加起來就是20的距離.

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

然后在setter方法中分別進(jìn)行判斷和設(shè)置:

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

大功告成啦!

是不是發(fā)現(xiàn)使用AutoLayout后cell自適應(yīng)的高度比設(shè)置frame時(shí)代簡單了不是一點(diǎn)半點(diǎn).

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

1.由于cell在estimatedHeightForRow...方法中拿到的只是估計(jì)的高度,滑動(dòng)屏幕的時(shí)候,tableView不斷拿到真實(shí)的高度對(duì)contentSize及滾動(dòng)條的大小等重新計(jì)算,由于實(shí)際值和預(yù)估值的偏差,可能導(dǎo)致滾動(dòng)條大小不穩(wěn)定甚至明顯跳動(dòng).

2.另外,如果使用的estimatedHeightForRow...方法后,如果你想滾動(dòng)到最后一行(比如聊天功能,可能在鍵盤彈上去后tableView滾到底部),也會(huì)計(jì)算不準(zhǔn).因?yàn)殚_啟估算高度胡,cell出現(xiàn)在屏幕上才會(huì)返回真實(shí)高度,如果根據(jù)indexPath直接跳轉(zhuǎn)到最后一行,后面的cell沒有出現(xiàn)在屏幕上過,依然是根據(jù)估算高度來算的,所以會(huì)導(dǎo)致滾動(dòng)的位置不準(zhǔn)確.

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容