Resources:
I've read multiple answers from Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
And followed their suggestions but it's not working.
Setup to reproduce:
If you copy/paste the MyTableViewCell
and ViewController
snippets: then you can reproduce the issue.
I have subclassed MyTableViewCell and added my own label.
import UIKit
class MyTableViewCell: UITableViewCell {
lazy var customLabel : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.numberOfLines = 0
return lbl
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupLayout()
}
private func setupLayout(){
contentView.addSubview(customLabel)
let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor)
let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 5)
let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
NSLayoutConstraint.activate([top, bottom, leadingFromImage, trailing])
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
The following ViewController
contains my tableview:
import UIKit
class ViewController: UIViewController {
var datasource = ["It would have been a great day had Manchester United Lost its \n game. Anyhow I hope tomorrow Arsenal will win the game"]
lazy var tableView : UITableView = {
let table = UITableView()
table.delegate = self
table.dataSource = self
table.translatesAutoresizingMaskIntoConstraints = false
table.estimatedRowHeight = 100
table.rowHeight = UITableViewAutomaticDimension
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.pinToAllEdges(of: view)
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.imageView?.image = UIImage(named: "honey")
cell.layoutSubviews()
cell.customLabel.preferredMaxLayoutWidth = tableView.bounds.width
logInfo(of: cell)
print("---------")
return cell
}
private func logInfo(of cell: MyTableViewCell){
print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
}
}
extension UIView{
func pinToAllEdges(of view: UIView){
let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
let top = topAnchor.constraint(equalTo: view.topAnchor)
let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([leading, top, trailing, bottom])
}
}
Link for honey image I used. I've set it's size to 44 * 44
Main issue
My major problem is inside cellForRowAtIndex
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.imageView?.image = UIImage(named: "honey")
cell.layoutSubviews()
cell.customLabel.preferredMaxLayoutWidth = cell.contentView.bounds.width
logInfo(of: cell)
print("---------")
return cell
}
Questions:
For whatever reason the value assigned to:
cell.customLabel.preferredMaxLayoutWidth
doesn't seem to be right.
Q1: Why is that?
Q2: I'm logging the contentView's bound before and after I call cell.layoutSubviews
and it switches from 320
to 260
but then eventually in the viewDebugger it shows up as 308
!!!
Why is the contenView's bounds changing again?!
I've removed some other screenshots from the question. They were mostly clutter but maybe worth looking. You can take a look at the revision history.
I believe the issue is related to using the default cell's
imageView
.The image view itself doesn't exist until its
.image
property is set, so on your cell init you're constraining the custom label to an image view that is 0,0,0,0Then, in
cellForRowAt
, you set the.image
property, and it appears that action also sets the contentView height. I can't find any docs on it, and digging through in debug I can't find any conflicting constraints, so I'm not entirely sure why that's happening.Two options:
1 - Instead of creating and adding a custom label, set the
.numberOfLines
on the default.textLabel
to0
. That should be enough.2 - If you need a customized label, also add a custom image view.
Option 2 is here:
Edit:
A couple more constraints are needed. If the cell has only enough text for one line (no wrapping), the imageView height will exceed the height of the cell:
So, we add top and bottom constraints to the imageView to fit at least the top and bottom of the cell:
and, it will probably look a little better with some padding, so we constrain the top and bottom of the imageView to be at least 4-pts from the top and bottom of the cell:
If desired, we can also "top-align" the text in the label by constraining its bottom to be at least 4-pts from the bottom, instead of exactly 4-pts from the bottom:
The comments in my edited code should explain each of those differences.