自定义 TableViewCell 触发 NSLayoutConstraint 错误 'UIView-Encapsulated-Layout-Height'
Custom TableViewCell triggers NSLayoutConstraint error 'UIView-Encapsulated-Layout-Height'
我对单元格使用自动高度。
每次我想用新数据(这里是模型变量)更新我的单元格时,我更新了我的自动布局约束,但我得到了一个错误。
这里只是为了说明问题,我什至没有更改约束。
我只是要求重新计算布局。
在单元格的第一次初始化时:没有警告,没问题。
错误:
<NSLayoutConstraint:0x174285d70 'UIView-Encapsulated-Layout-Height'
UITableViewCellContentView:0x1030f8a50.height == 500 (active)>
代码:
tableView.rowHeight = UITableViewAutomaticDimension
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 500
}
class TestTableViewCell: UITableViewCell {
var model: X? {
didSet {
setNeedsLayout()
layoutIfNeeded()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let viewtop = UIView()
let viewbottom = UIView()
viewtop.backgroundColor = UIColor.yellow
viewbottom.backgroundColor = UIColor.red
contentView.backgroundColor = UIColor.blue
contentView.addSubview(viewtop)
contentView.addSubview(viewbottom)
viewtop.snp.makeConstraints { (make) in
make.top.equalTo(contentView)
make.left.right.equalTo(contentView)
make.height.equalTo(50)
}
viewbottom.snp.makeConstraints { (make) in
make.top.equalTo(viewtop.snp.bottom)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView)
make.height.equalTo(120)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
问题:
为什么在询问相同约束的重新布局后,我会收到错误消息?
编辑:这里是另一个更好理解的例子。
var botViewHeightConstraint:Constraint!
class TestTableViewCell: UITableViewCell {
var model: Int? {
didSet {
if model == 1 {
botViewHeightConstraint.update(offset:200)
}else{
botViewHeightConstraint.update(offset:120)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let viewtop = UIView()
let viewbottom = UIView()
viewtop.backgroundColor = UIColor.yellow
viewbottom.backgroundColor = UIColor.red
contentView.backgroundColor = UIColor.blue
contentView.addSubview(viewtop)
contentView.addSubview(viewbottom)
viewtop.snp.makeConstraints { (make) in
make.top.equalTo(contentView)
make.left.right.equalTo(contentView)
make.height.equalTo(50)
}
viewbottom.snp.makeConstraints { (make) in
make.top.equalTo(viewtop.snp.bottom)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView)
botViewHeightConstraint = make.height.equalTo(120).constraint
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
CellForRow 代码:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = fetchedResultsController?.fetchedObjects?[(indexPath as NSIndexPath).section] {
let cell = tableView.dequeueReusableCell(withIdentifier: imagePostCellId) as! TestTableViewCell!
cell.model = 1
return cell
}
}
为什么要添加 "make.height.equalTo(120)" 已经设置了相对于内容和顶部视图的左上和右下约束
根据您的代码,单元格高度似乎始终为 50+120。
另请参阅覆盖 heightForRow 和 return UITableViewAutomaticDimension 是否有效。
首先,您一直忽略将子视图的 translatesAutoresizingMaskIntoConstraints
设置为 false
。这很重要。 (不过,也许 snapkit 会为您做到这一点。我不知道。)
其次——这是重点——你所做的不是从内到外调整可变高度单元格的大小。您不会像您的代码那样更改绝对高度约束。大小最终基于子视图的 内在内容大小 。当然,某些子视图可能具有绝对高度,但最终必须至少有一个具有固有内容大小。 是您可以动态更改以确定单元格高度的大小。
这就是为什么,例如,包含 UILabel 的单元格很容易与动态行高一起使用。它具有固有的内容大小。
内在内容大小与单元格的内置高度(控制台转储中的 UIView-Encapsulated-Layout-Height
)不冲突;它 补充 它(当运行时在幕后调用 systemLayoutSizeFitting(UILayoutFittingCompressedSize)
时,这就是自动可变行高的工作方式)。
如果您使用带有 intrinsicContentSize
实现的自定义子视图,并且如果在单元格中设置模型值会触发对 invalidateIntrinsicContentSize
的调用,您的示例将完美运行,控制台中不会出现任何投诉.
以下是此类自定义视图的示例:
class MyView : UIView {
var h : CGFloat = 200 {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return CGSize(width:300, height:self.h)
}
}
当这是单元格内容视图的子视图时,在 cellForRow
中设置此视图的 h
可以正确调整单元格的高度。
例如,假设我们单元格的内容视图只有一个子视图,v
,它是一个 MyView。那么:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCell
let even = indexPath.row % 2 == 0
cell.v.backgroundColor = even ? .red : .green
cell.v.h = even ? 40 : 80 // triggers layout!
return cell
}
我对单元格使用自动高度。 每次我想用新数据(这里是模型变量)更新我的单元格时,我更新了我的自动布局约束,但我得到了一个错误。 这里只是为了说明问题,我什至没有更改约束。 我只是要求重新计算布局。
在单元格的第一次初始化时:没有警告,没问题。
错误:
<NSLayoutConstraint:0x174285d70 'UIView-Encapsulated-Layout-Height'
UITableViewCellContentView:0x1030f8a50.height == 500 (active)>
代码:
tableView.rowHeight = UITableViewAutomaticDimension
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 500
}
class TestTableViewCell: UITableViewCell {
var model: X? {
didSet {
setNeedsLayout()
layoutIfNeeded()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let viewtop = UIView()
let viewbottom = UIView()
viewtop.backgroundColor = UIColor.yellow
viewbottom.backgroundColor = UIColor.red
contentView.backgroundColor = UIColor.blue
contentView.addSubview(viewtop)
contentView.addSubview(viewbottom)
viewtop.snp.makeConstraints { (make) in
make.top.equalTo(contentView)
make.left.right.equalTo(contentView)
make.height.equalTo(50)
}
viewbottom.snp.makeConstraints { (make) in
make.top.equalTo(viewtop.snp.bottom)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView)
make.height.equalTo(120)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
问题: 为什么在询问相同约束的重新布局后,我会收到错误消息?
编辑:这里是另一个更好理解的例子。
var botViewHeightConstraint:Constraint!
class TestTableViewCell: UITableViewCell {
var model: Int? {
didSet {
if model == 1 {
botViewHeightConstraint.update(offset:200)
}else{
botViewHeightConstraint.update(offset:120)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let viewtop = UIView()
let viewbottom = UIView()
viewtop.backgroundColor = UIColor.yellow
viewbottom.backgroundColor = UIColor.red
contentView.backgroundColor = UIColor.blue
contentView.addSubview(viewtop)
contentView.addSubview(viewbottom)
viewtop.snp.makeConstraints { (make) in
make.top.equalTo(contentView)
make.left.right.equalTo(contentView)
make.height.equalTo(50)
}
viewbottom.snp.makeConstraints { (make) in
make.top.equalTo(viewtop.snp.bottom)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView)
botViewHeightConstraint = make.height.equalTo(120).constraint
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
CellForRow 代码:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = fetchedResultsController?.fetchedObjects?[(indexPath as NSIndexPath).section] {
let cell = tableView.dequeueReusableCell(withIdentifier: imagePostCellId) as! TestTableViewCell!
cell.model = 1
return cell
}
}
为什么要添加 "make.height.equalTo(120)" 已经设置了相对于内容和顶部视图的左上和右下约束
根据您的代码,单元格高度似乎始终为 50+120。
另请参阅覆盖 heightForRow 和 return UITableViewAutomaticDimension 是否有效。
首先,您一直忽略将子视图的 translatesAutoresizingMaskIntoConstraints
设置为 false
。这很重要。 (不过,也许 snapkit 会为您做到这一点。我不知道。)
其次——这是重点——你所做的不是从内到外调整可变高度单元格的大小。您不会像您的代码那样更改绝对高度约束。大小最终基于子视图的 内在内容大小 。当然,某些子视图可能具有绝对高度,但最终必须至少有一个具有固有内容大小。 是您可以动态更改以确定单元格高度的大小。
这就是为什么,例如,包含 UILabel 的单元格很容易与动态行高一起使用。它具有固有的内容大小。
内在内容大小与单元格的内置高度(控制台转储中的 UIView-Encapsulated-Layout-Height
)不冲突;它 补充 它(当运行时在幕后调用 systemLayoutSizeFitting(UILayoutFittingCompressedSize)
时,这就是自动可变行高的工作方式)。
如果您使用带有 intrinsicContentSize
实现的自定义子视图,并且如果在单元格中设置模型值会触发对 invalidateIntrinsicContentSize
的调用,您的示例将完美运行,控制台中不会出现任何投诉.
以下是此类自定义视图的示例:
class MyView : UIView {
var h : CGFloat = 200 {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return CGSize(width:300, height:self.h)
}
}
当这是单元格内容视图的子视图时,在 cellForRow
中设置此视图的 h
可以正确调整单元格的高度。
例如,假设我们单元格的内容视图只有一个子视图,v
,它是一个 MyView。那么:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCell
let even = indexPath.row % 2 == 0
cell.v.backgroundColor = even ? .red : .green
cell.v.h = even ? 40 : 80 // triggers layout!
return cell
}