iOS 利用约束布局自适应TableViewCell高度
概述
有时候会遇到类似下图的需求,要求根据文本自适应Cell高度
效果.png此时可以利用以下两个特性来很方便的实现Cell高度自适应
1.TableView的代理方法cellForRow
调用在heightForRow
之前,所以可以设置Cell的Model时通过提前调用layoutIfNeeded()
来让约束布局自动帮我们计算Cell高度。
2.约束布局有一个特点,某个View的约束在只受自身影响时,是可以被该View内部的约束撑开的
这样做的好处是,比通过各种方式提前计算Cell高度误差更小,并且更轻松,代码量更少。
在该样例中Cell约束如何设置
首先需要明确一点,很多时候我们都是直接在TableViewCell的ContentView中布局,而要利用上述的约束布局特点,需要将所有子控件加入另外一个View(下文统称该View为Container
)中布局,再将Container加入Cell的ContentView。
1.Container内约束布局
布局1
TitleLabel和图片View都是固定宽高的不多做阐述,重点放在ContentLabel。
有以下两个注意点
首先,ContentLabel的约束只有上下左右4个,不能加宽高,不然会影响自适应计算结果。
其次,ContentLabel的numberOfLines
属性必须设置为0,才能根据内容自动换行
这样设置后理论上Container在没有外部约束影响、并且宽度确定的情况下
最终的高度就取决于ContentLabel具体有几行。因为其他控件是确定的,Container会被ContentLabel的上下约束给撑开
2.Container外约束布局
布局2.png 布局3.png
Container的约束需要注意以下几点
首先,Container只能添加到父布局左上来确定位置,一定不能添加到父布局底部的约束,不然会导致Container受到外部约束影响固定高度而无法根据内部约束撑开,进而影响自动计算高度
其次,如图所示,Container的宽度需要通过一个宽度约束来设置,并且需要在计算前设置成真实的屏幕尺寸(setModel时,可以参考下面代码样例
)。Container到父布局右侧的约束必须勾选Remove at build time(只在xib中显示,实际运行时不会生效)
或者直接不设置
。如果这个步骤出错,会导致自动计算的宽度是以xib的宽度为准,进而导致自动计算的高度有误差。
这种约束的核心思想是内部撑开外部,和平时的先通过各种计算提前获得Cell整体高度然后布局直接上下左右贴着Cell的ContentView最终通过ContentView来拉开内部控件有点不同。
这种约束不设置Container的高,因为会通过自动计算并且设置成Cell的高度,最终显示时Cell高度和Container高度是一致的,所以不需要设置到ContentView底部的约束(靠父布局拉开)
代码样例
1.Model
class CellModel: NSObject {
var title:String
var content:String
var cellHeight:CGFloat = 0
init(title:String, content:String) {
self.title = title
self.content = content
super.init()
}
}
2.Cell
import UIKit
class CustomCell: UITableViewCell {
@IBOutlet weak var container: UIView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var contentLabel: UILabel!
@IBOutlet weak var containerWidthLayout: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
self.containerWidthLayout.constant = UIScreen.main.bounds.width
self.selectionStyle = .none
}
func setModel(model:CellModel){
self.titleLabel.text = model.title
self.contentLabel.text = model.content
if(model.cellHeight == 0){
self.container.layoutIfNeeded()
model.cellHeight = self.container.bounds.size.height
}
}
}
3.ViewController
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableview: UITableView!
var data:Array<CellModel> = []
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
let nib = UINib.init(nibName: "CustomCell", bundle: Bundle.main)
tableview.register(nib, forCellReuseIdentifier: "CustomCellID")
initData()
}
func initData(){
let model1 = CellModel.init(title: "测试短", content: "测试内容短")
let model2 = CellModel.init(title: "测试中", content: "测试内容中测试内容中测试内容中测试内容中测试内容中测试内容中测试内容中测试内容中")
let model3 = CellModel.init(title: "测试长", content: "测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长测试内容长")
self.data.append(model1)
self.data.append(model2)
self.data.append(model3)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let model = data[indexPath.row]
return model.cellHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCellID") as! CustomCell
let model = data[indexPath.row]
cell.setModel(model: model)
return cell
}
}