前提
- 本文以
Swift作為講解,OC類似,不作贅述 - 我們這里討論的是純代碼方式的布局,所以
XIB和Storyboard的使用方式不在本文的討論范疇。
問(wèn)題描述
通常情況下,我們都是通過(guò)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
提前告知每個(gè)Cell的高度。
當(dāng) UITableViewCell 中的內(nèi)容出現(xiàn)圖文混排時(shí),每個(gè)的Cell高度不盡相同。比較笨的做法,是使用 String 的自動(dòng)計(jì)算文本的高度(實(shí)際上是寬高都計(jì)算了,也就是size)
下面是根據(jù)字體大小計(jì)算系統(tǒng)默認(rèn)字體的 字符串 所占的寬度
// MARK: 計(jì)算字符鎖占的寬度
func calculateStringWidth(sourceString: String?, fontSize: CGFloat) -> CGFloat {
guard let _ = sourceString else {
return 0
}
let size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))
let rect: CGRect = sourceString!.boundingRect(with: size, options: NSStringDrawingOptions.usesFontLeading, attributes: [kCTFontAttributeName as NSAttributedStringKey: UIFont.systemFont(ofSize: fontSize)], context: nil);
return rect.width
}
事實(shí)上,就圖文混排的UITableViewCell 而言,特別需要高度的自適應(yīng)。
解決的第三方庫(kù)
使用的第三方庫(kù) UITableView-FDTemplateLayoutCell
解決思路
這里的思路是利用 autoLayout(實(shí)際上就是OC中的Masonry,Swift中的SnapKit) 寫好約束布局,在tableViewDelegate的高度和cell實(shí)現(xiàn)中作充數(shù)據(jù)填充(我這里叫render渲染),自動(dòng)把高度撐開的過(guò)程。
實(shí)例代碼:
TableView 中的代理
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.fd_heightForCell(withIdentifier: titleWithRightImageCellID, configuration: { (cell) in
let currentCell = cell as! ArticleTitlWithRightImageCell
currentCell.renderCell(with: self.testDataSource[indexPath.row])
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = (tableView.dequeueReusableCell(withIdentifier: titleWithRightImageCellID,
for: indexPath)) as! ArticleTitlWithRightImageCell
cell.renderCell(with: testDataSource[indexPath.row])
return cell
}
可以看到,在兩個(gè)代理中調(diào)用了同一個(gè)渲染。渲染的實(shí)質(zhì)是讓其中的
UILabel和UIImage之類的數(shù)據(jù)重賦值。涉及之前有提到過(guò)的復(fù)用機(jī)制,這了不贅述。除此之外,渲染還做了一些約束的重置,因?yàn)橐恍┣闆r下,cellmodel對(duì)應(yīng)的那個(gè)數(shù)據(jù)并不存在,需要隱藏
另外,實(shí)際上 UITableView-FDTemplateLayoutCell 在 fd_heightForCell 中提供的方法不只是 fd_heightForCell一種,還有其他帶緩存的方法,因?yàn)闆](méi)有深入研究,這里不做展開了。
Cell 中的約束示例
private func setupConstrains() {
titleLabel.snp.makeConstraints { (make) in
make.top.equalTo(contentView.snp.top).offset(16).priority(.high)
make.leading.equalTo(11)
make.trailing.equalTo(-11)
}
imageViewContainer.snp.makeConstraints { (make) in
make.left.right.equalTo(contentView)
make.top.equalTo(contentLabel.snp.bottom).offset(10)
make.height.equalTo(imageHeight)
}
footerView.snp.makeConstraints { (make) in
make.height.equalTo(46)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView.snp.bottom).priority(.high)
make.top.equalTo(imageViewContainer.snp.bottom)
}
}
約束中,務(wù)必注意以下幾點(diǎn),否則約束一旦出錯(cuò),就有可能無(wú)法撐開高度。
-
Cell需要使用tableView的registerNib或者registerClass的方法進(jìn)行注冊(cè),否則會(huì)報(bào)異常。 - 添加控件必須使用
self.contentView.addSubView- 所有的控件,必須添加到
cell.contentView中去! - 所有的控件,約束也必須依賴于
cell.contentView而非cell,否則會(huì)出錯(cuò)
- 所有的控件,必須添加到
- 頂部和底部的約束必須寫好
- 最上面的控件,要寫針對(duì)
contentView.snp.top的依賴,并設(shè)置最高權(quán)限priority(.high) - 最下面的控件,要寫針對(duì)
contentView.snp.bottom的依賴,并設(shè)置最高權(quán)限priority(.high)
- 最上面的控件,要寫針對(duì)
如果不添加最高權(quán)限,你會(huì)發(fā)現(xiàn),我們的布局其實(shí)是正確的,但是一直會(huì)報(bào)一個(gè)類似
<NSLayoutConstraint UITableViewCellContentView.height == 44>的約束沖突。實(shí)際測(cè)試發(fā)現(xiàn),是因?yàn)?heightForCell默認(rèn)是一個(gè)44高度,我們?cè)诨卣{(diào)中使用了約束渲染把他撐開,他還是會(huì)報(bào)錯(cuò)
解決方案就是在頂部和底部添加最高約束權(quán)限。github issues 參考鏈接