有了預(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ì)這方面要求不是特別高,一般的需求是可以滿足了.