Swift 4.2 TableViewCell动态高度编程
Swift 4.2 TableViewCell dynamic height programmatically
这不是重复的问题,因为这个问题没有真正的解决方案
我正在尝试使用约束通过其内容实现 UITableViewcell
动态高度,但收到布局警告:
Will attempt to recover by breaking constraint
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints
to catch this in the debugger. The methods in the
UIConstraintBasedLayoutDebugging category on UIView listed in
may also be helpful. 2019-03-15
12:27:52.085475+0400 TableCellDynamicHeight[31984:1295380]
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want. Try this: (1) look at each constraint and try to
figure out which you don't expect; (2) find the code that added the
unwanted constraint or constraints and fix it. (
"",
"",
"",
"" )
我检查了一些线程:
UITableView 单元格的动态高度问题 (Swift)
什么是正确的解决方案,我错过了什么?
ViewController:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView: UITableView = {
let table = UITableView()
table.backgroundColor = .white
table.translatesAutoresizingMaskIntoConstraints = false
table.register(TableViewCell.self, forCellReuseIdentifier: "cellId")
table.dataSource = self
table.delegate = self
return table
}()
let arr:[Int:UIColor] = [345: UIColor.random, 422: .random, 23: .random, 344: .random,200: .random,140: .random]
var pickerDataVisitLocation = [203: "Home", 204: "Hospital", 205: "Other"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.view.addSubview(tableView)
//
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.tableFooterView = UIView()
}
}
extension ViewController {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
let value:UIColor = Array(arr)[indexPath.row].value
let key = Array(arr)[indexPath.row].key
cell.setupViews(he: CGFloat(key), color: value)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1.0)
}
}
TableViewCell:
import UIKit
class TableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupViews(he:CGFloat, color:UIColor) {
let v:UIView = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(v)
v.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
v.backgroundColor = color
v.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
v.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
v.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
v.heightAnchor.constraint(equalToConstant: he).isActive = true
#warning("here is constraint error conflict with bottomAnchor and heightAnchor, need correct solution")
}
}
在您的情况下,数据源 arr
中提供了高度,因此您不需要:
- 身高限制
estimatedHeightForRowAtIndexPath
你只需要 return heightForRowAtIndexPath
中的实际高度,但首先你的数据源 arr:[Int:UIColor]
是一个 Dictionary
,我不会依赖它的顺序,让将其更改为 Tuples
的 Array
:
var dataSource: [(height: CGFloat, color: UIColor)] = [
(345, .random),
(422, .random),
(23, .random),
(344, .random),
(200, .random),
(140, .random)
]
现在使用以下 UITableView Delegate/DataSource 方法:
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return dataSource[indexPath.row].height
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
cell.setupViews(color: dataSource[indexPath.row].color)
return cell
}
}
由于您不需要高度限制,我从 setupViews
方法
中删除了 he
参数
你做错了几件事...
首先,单元格被重用(因此 dequeueReusableCell
),但是您的 setupViews()
函数会在每次重用单元格时添加一个新的子视图 。
这意味着当您滚动并重复使用单元格时,您最终会得到 2、3、4 ... 十几个子视图,所有子视图都具有相互冲突的约束。
将您的 addSubview()
移动到单元格中的通用初始化函数,这样视图只会创建和添加一次。
这也是您应该设置约束的地方。
要在您的应用程序设计时更改子视图的高度,您需要更改子视图高度约束上的.constant
。
这是您修改后的代码。我在代码中添加了足够多的注释,应该很清楚了:
class HattoriTableViewCell: UITableViewCell {
// the view to add as a subview
let myView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// the constraint we'll use for myView's height
var myViewHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// add the subview
self.addSubview(myView)
// constrain it to all 4 sides
myView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
myView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
myView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
myView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// create the height constraint
myViewHeightConstraint = myView.heightAnchor.constraint(equalToConstant: 1)
// needs Priority less-than 1000 (default) to avoid breaking constraints
myViewHeightConstraint.priority = UILayoutPriority.init(999)
// activate it
myViewHeightConstraint.isActive = true
}
func setupViews(he:CGFloat, color:UIColor) {
// set myView's background color
myView.backgroundColor = color
// change myView's height constraint constant
myViewHeightConstraint.constant = he
}
}
class HattoriViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView: UITableView = {
let table = UITableView()
table.backgroundColor = .white
table.translatesAutoresizingMaskIntoConstraints = false
table.register(HattoriTableViewCell.self, forCellReuseIdentifier: "cellId")
table.dataSource = self
table.delegate = self
return table
}()
let arr:[Int:UIColor] = [345: UIColor.random, 422: .random, 23: .random, 344: .random,200: .random,140: .random]
var pickerDataVisitLocation = [203: "Home", 204: "Hospital", 205: "Other"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.view.addSubview(tableView)
//
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.tableFooterView = UIView()
// use a reasonable value -- such as the average of what you expect (if known)
tableView.estimatedRowHeight = 200
}
}
extension HattoriViewController {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! HattoriTableViewCell
let value:UIColor = Array(arr)[indexPath.row].value
let key = Array(arr)[indexPath.row].key
cell.setupViews(he: CGFloat(key), color: value)
return cell
}
// NOT NEEDED
// func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// return UITableView.automaticDimension
// }
//
// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
// return UITableView.automaticDimension
// }
}
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1.0)
}
}
我遇到了同样的问题,但出于不同的原因,我想与您分享。
我只是重写 layoutSubviews()
方法来添加我的自定义布局。
但后来我没有在初始化程序中调用 layoutIfNeeded()
方法来激活它们,而是仅在单元格出队和重新使用时才激活布局。
如果您遇到同样的问题,请参考我的代码:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubviews(containerView)
containerView.addSubviews(titleLabel, subtitleLabel, activteSwitch)
layoutIfNeeded() // Required to triger the overriden layoutSubviews() upon initialization
}
// However, I shouldn't override this method or add any constraints here
override func layoutSubviews() {
let margin: CGFloat = 8
containerView.snapToEdges()
NSLayoutConstraint.activate([
activteSwitch.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -margin),
activteSwitch.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: margin),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: margin),
titleLabel.trailingAnchor.constraint(equalTo: activteSwitch.leadingAnchor, constant: -margin),
subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: margin),
subtitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: margin),
subtitleLabel.trailingAnchor.constraint(equalTo: activteSwitch.leadingAnchor, constant: -margin),
subtitleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -margin)
])
}
这不是重复的问题,因为这个问题没有真正的解决方案
我正在尝试使用约束通过其内容实现 UITableViewcell
动态高度,但收到布局警告:
Will attempt to recover by breaking constraint
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful. 2019-03-15 12:27:52.085475+0400 TableCellDynamicHeight[31984:1295380] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "", "", "", "" )
我检查了一些线程:
UITableView 单元格的动态高度问题 (Swift)
什么是正确的解决方案,我错过了什么?
ViewController:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView: UITableView = {
let table = UITableView()
table.backgroundColor = .white
table.translatesAutoresizingMaskIntoConstraints = false
table.register(TableViewCell.self, forCellReuseIdentifier: "cellId")
table.dataSource = self
table.delegate = self
return table
}()
let arr:[Int:UIColor] = [345: UIColor.random, 422: .random, 23: .random, 344: .random,200: .random,140: .random]
var pickerDataVisitLocation = [203: "Home", 204: "Hospital", 205: "Other"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.view.addSubview(tableView)
//
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.tableFooterView = UIView()
}
}
extension ViewController {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
let value:UIColor = Array(arr)[indexPath.row].value
let key = Array(arr)[indexPath.row].key
cell.setupViews(he: CGFloat(key), color: value)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1.0)
}
}
TableViewCell:
import UIKit
class TableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupViews(he:CGFloat, color:UIColor) {
let v:UIView = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(v)
v.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
v.backgroundColor = color
v.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
v.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
v.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
v.heightAnchor.constraint(equalToConstant: he).isActive = true
#warning("here is constraint error conflict with bottomAnchor and heightAnchor, need correct solution")
}
}
在您的情况下,数据源 arr
中提供了高度,因此您不需要:
- 身高限制
estimatedHeightForRowAtIndexPath
你只需要 return heightForRowAtIndexPath
中的实际高度,但首先你的数据源 arr:[Int:UIColor]
是一个 Dictionary
,我不会依赖它的顺序,让将其更改为 Tuples
的 Array
:
var dataSource: [(height: CGFloat, color: UIColor)] = [
(345, .random),
(422, .random),
(23, .random),
(344, .random),
(200, .random),
(140, .random)
]
现在使用以下 UITableView Delegate/DataSource 方法:
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return dataSource[indexPath.row].height
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
cell.setupViews(color: dataSource[indexPath.row].color)
return cell
}
}
由于您不需要高度限制,我从 setupViews
方法
he
参数
你做错了几件事...
首先,单元格被重用(因此 dequeueReusableCell
),但是您的 setupViews()
函数会在每次重用单元格时添加一个新的子视图 。
这意味着当您滚动并重复使用单元格时,您最终会得到 2、3、4 ... 十几个子视图,所有子视图都具有相互冲突的约束。
将您的 addSubview()
移动到单元格中的通用初始化函数,这样视图只会创建和添加一次。
这也是您应该设置约束的地方。
要在您的应用程序设计时更改子视图的高度,您需要更改子视图高度约束上的.constant
。
这是您修改后的代码。我在代码中添加了足够多的注释,应该很清楚了:
class HattoriTableViewCell: UITableViewCell {
// the view to add as a subview
let myView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// the constraint we'll use for myView's height
var myViewHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// add the subview
self.addSubview(myView)
// constrain it to all 4 sides
myView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
myView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
myView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
myView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// create the height constraint
myViewHeightConstraint = myView.heightAnchor.constraint(equalToConstant: 1)
// needs Priority less-than 1000 (default) to avoid breaking constraints
myViewHeightConstraint.priority = UILayoutPriority.init(999)
// activate it
myViewHeightConstraint.isActive = true
}
func setupViews(he:CGFloat, color:UIColor) {
// set myView's background color
myView.backgroundColor = color
// change myView's height constraint constant
myViewHeightConstraint.constant = he
}
}
class HattoriViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView: UITableView = {
let table = UITableView()
table.backgroundColor = .white
table.translatesAutoresizingMaskIntoConstraints = false
table.register(HattoriTableViewCell.self, forCellReuseIdentifier: "cellId")
table.dataSource = self
table.delegate = self
return table
}()
let arr:[Int:UIColor] = [345: UIColor.random, 422: .random, 23: .random, 344: .random,200: .random,140: .random]
var pickerDataVisitLocation = [203: "Home", 204: "Hospital", 205: "Other"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.view.addSubview(tableView)
//
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.tableFooterView = UIView()
// use a reasonable value -- such as the average of what you expect (if known)
tableView.estimatedRowHeight = 200
}
}
extension HattoriViewController {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! HattoriTableViewCell
let value:UIColor = Array(arr)[indexPath.row].value
let key = Array(arr)[indexPath.row].key
cell.setupViews(he: CGFloat(key), color: value)
return cell
}
// NOT NEEDED
// func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// return UITableView.automaticDimension
// }
//
// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
// return UITableView.automaticDimension
// }
}
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1.0)
}
}
我遇到了同样的问题,但出于不同的原因,我想与您分享。
我只是重写 layoutSubviews()
方法来添加我的自定义布局。
但后来我没有在初始化程序中调用 layoutIfNeeded()
方法来激活它们,而是仅在单元格出队和重新使用时才激活布局。
如果您遇到同样的问题,请参考我的代码:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubviews(containerView)
containerView.addSubviews(titleLabel, subtitleLabel, activteSwitch)
layoutIfNeeded() // Required to triger the overriden layoutSubviews() upon initialization
}
// However, I shouldn't override this method or add any constraints here
override func layoutSubviews() {
let margin: CGFloat = 8
containerView.snapToEdges()
NSLayoutConstraint.activate([
activteSwitch.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -margin),
activteSwitch.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: margin),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: margin),
titleLabel.trailingAnchor.constraint(equalTo: activteSwitch.leadingAnchor, constant: -margin),
subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: margin),
subtitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: margin),
subtitleLabel.trailingAnchor.constraint(equalTo: activteSwitch.leadingAnchor, constant: -margin),
subtitleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -margin)
])
}