I want the cells of a UITableView
to adapt to the size of their content in iOS 10 and 11 with:
tableView.estimatedRowHeight = UITableViewAutomaticDimension // default in iOS 11
tableView.rowHeight = UITableViewAutomaticDimension
Without setting the tableView.rowHeight
to an explicit numerical value which is the new default in iOS 11.
A UIView
has no intrinsic content size, therefore I set a layout constraint for its height anchor. However that anchor breaks during run time.
What internal constraints in the UITableViewCell
are necessary for the cell to adapt to its content?
This works in iOS 11 only:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.estimatedRowHeight = UITableViewAutomaticDimension
tableView.rowHeight = UITableViewAutomaticDimension
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(view)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
view.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor),
view.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor),
view.heightAnchor.constraint(equalToConstant: 300)
])
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
In iOS 10 it throws this runtime error and the cell size doesn't adapt:
[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. ( "NSLayoutConstraint:0x17009c020 V:|-(0)-[test.MyView:0x11dd1acb0] (active, names: '|':UITableViewCellContentView:0x11dd15220 )", "NSLayoutConstraint:0x17009c160 test.MyView:0x11dd1acb0.bottom == UITableViewCellContentView:0x11dd15220.bottom (active)", "NSLayoutConstraint:0x17009c390 test.MyView:0x11dd1acb0.height == 300 (active)", "NSLayoutConstraint:0x17009be40 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x11dd15220.height == 43.6667 (active)" )
Will attempt to recover by breaking constraint NSLayoutConstraint:0x17009c390 test.MyView:0x11dd1acb0.height == 300 (active)
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in UIKit/UIView.h may also be helpful.
This works in iOS 10 and 11 but does not use the new iOS 11 approach as tableView.estimatedRowHeight
is not UITableViewAutomaticDimension
:
class ViewController_TableView: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.estimatedRowHeight = 30
tableView.rowHeight = UITableViewAutomaticDimension
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
let view = MyView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(view)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
view.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor),
view.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor)
])
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
class MyView: UIView {
override var intrinsicContentSize: CGSize {
get {
return CGSize(width: 300, height: 300)
}
}
}