最近在工作中遇到了一個問題,就是在AutoLayout
中如何使UITableViewCell
的行高根據(jù)內(nèi)容(具體就是UILabel
的多行顯示)達(dá)到自適應(yīng)高度榛丢。google so一通找到了一些資料,但都不是特別齊全宜岛,特別是針對iOS7的兼容上姜贡,因此也踩了不少坑,在這里做一個總結(jié)磷支。
首先看一下效果
其實(shí)在iOS8以上谒撼,cell的動態(tài)高度的實(shí)現(xiàn)已經(jīng)變的很方便了,只需要你在cell里面正確設(shè)置約束雾狈,再設(shè)置tableview
的幾個屬性廓潜,就大功告成了!
首先正確設(shè)置約束
這里要注意的就是善榛,一定要確保在設(shè)置約束之前辩蛋,你
UITableViewCell
的size inspector里面 Row Height 是Default而不是custom的數(shù)值,否則之后不管你如何操作移盆,UITableViewCell
優(yōu)先使用的都是custom的數(shù)值
還有一點(diǎn)要注意的是悼院,如果你和我一樣,是
UILabel
需要多行顯示造成的行高不固定咒循,那么你的UILabel
的行數(shù)要設(shè)置為0据途,表示UILabel
顯示的是多行。
最后在viewDidLoad中加上
self.tableView.estimatedRowHeight = 56
self.tableView.rowHeight = UITableViewAutomaticDimension
estimatedRowHeight
是假定的高度叙甸,因?yàn)樾枰A(yù)估UITableView
的UIScrollView
的contentSize
颖医。因此這種方法可能潛在的問題就是數(shù)據(jù)量大的時候滾動條可能會閃動。具體的解決方法還沒有研究裆蒸,應(yīng)該可以通過UIScrollView
的代理方法解決熔萧。UITableViewAutomaticDimension
這一句在iOS8+是作為rowHeight
的默認(rèn)值的,這句話也可以不寫僚祷。
至此iOS8+ AutoLayout的動態(tài)行高就大功告成了哪痰,你甚至可以不用去實(shí)現(xiàn)heightForRowAtIndexPath
。
但是iOS7的兼容就顯得蛋疼許多了久妆,主要是由于晌杰,iOS7使用UITableView一定要實(shí)現(xiàn)heightForRowAtIndexPath
代理方法,這里你可能會覺得筷弦,那我們實(shí)現(xiàn)這個代理方法肋演,返回UITableViewAutomaticDimension
就好了啊抑诸,遺憾的是UITableViewAutomaticDimension
在iOS7里面也是不可用的,不信你可以試一下爹殊,直接crash嫩舟。
所以我們的思路得這樣走付呕,實(shí)現(xiàn)heightForRowAtIndexPath
, 創(chuàng)建一個臨時的cell,設(shè)置cell里面各個view的屬性果覆,主動觸發(fā)layout,通過UIView的方法systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
獲取cell的實(shí)際尺寸衔瓮,作為返回的高度存捺。代碼看起來像下面這樣.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//兼容ios7
if NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {
let mockcell = tableView.dequeueReusableCellWithIdentifier(youtidentifier)
//
//設(shè)置你的cell的子view,比如UILabel的title
//
let height = mockcell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
return height
}
}
else {
return UITableViewAutomaticDimension
}
}
上面的代碼需要注意的一點(diǎn)就是铅碍,tableView.dequeueReusableCellWithIdentifier
這個方法千萬不能傳入indexPath
润绵,否則會死循環(huán),一直調(diào)用該方法胞谈。
上面的代碼其實(shí)還有一個很明顯的問題尘盼,就是每一次計(jì)算高度都需要創(chuàng)建一次UITableViewCell,我們可以做一個簡單的改進(jìn)烦绳,用一個Dictionary<NSIndexPath,CGFloat>
存儲計(jì)算過的indexPath對應(yīng)的height卿捎,代碼如下。
private var heightOfIndex = [NSIndexPath:CGFloat]()
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//兼容ios7
if NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {
if let height = self.heightOfIndex[indexPath] {
return height
} else {
let mockcell = tableView.dequeueReusableCellWithIdentifier(youtidentifier)
//
//設(shè)置你的cell的子view径密,比如UILabel的title
//
let height = mockcell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
self.heightOfIndex[indexPath] = height
return height
}
}
else {
return UITableViewAutomaticDimension
}
}
為了能夠代碼重用娇澎,我們可以把他封裝成一個類,設(shè)置cell的過程作為一個block傳入睹晒。
class ALTableViewCellHeight {
private var heightOfIndex = [NSIndexPath:CGFloat]()
func heightForRowAtIndexPath(indexPath:NSIndexPath,initCell:()->UITableViewCell) -> CGFloat {
if NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {
if let height = self.heightOfIndex[indexPath] {
return height
} else {
let cell = initCell()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
self.heightOfIndex[indexPath] = height
return height
}
}
else {
return UITableViewAutomaticDimension
}
}
}
最后再說說iOS7下UILabel的一個坑趟庄,如果要使你的UILabel能夠正常的多行顯示,除了一開始說到的設(shè)置numberOfLines
為0伪很,還需要設(shè)置preferredMaxLayoutWidth
戚啥,這個屬性指的當(dāng)換行的最大寬度。iOS8+能夠通過AutoLayout計(jì)算出UILabel
的寬度锉试,把這個寬度作為preferredMaxLayoutWidth
猫十,但是iOS7下面不行,應(yīng)該是一個bug呆盖。解決的方案是繼承UILabel
拖云,重寫layoutSubviews
。
class LabelDynamicHeight:UILabel {
override func layoutSubviews() {
super.layoutSubviews()
self.preferredMaxLayoutWidth = self.frame.size.width
super.layoutSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.numberOfLines = 0
}
}