4
votes

I have a UITableView that I fill with autosizing cells. UITableView setup is fairly simple:

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension

Exactly like Apple recommends here: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html

To enable self-sizing table view cells, you must set the table view’s rowHeight property to UITableViewAutomaticDimension. You must also assign a value to the estimatedRowHeight property. As soon as both of these properties are set, the system uses Auto Layout to calculate the row’s actual height.

When configuring a cell I also disable/enable some constraints to achieve the needed look. That’s where things get interesting. Cell layout is not updated until the cell is reused. Literally. You can call layoutIfNeeded(), setNeedsLayout(), layoutSubviews() or any other method there is, there is no way you will force the cell to update its layout.

All other aspects work pretty good: labels do change their text, you hide/unhide the views, but layout is stuck until the cell is reused.

Question: what causes it and how to avoid this behavior?

3

3 Answers

4
votes

Unfortunately, none of the provided answers/comments worked out for me. I always ended up with an initially incorrect layout. Only after reusing the cell, or calling reloadData() (on the table view) it was displayed correctly.

The following was the only thing, that worked for me in the end. I'm not a big fan of such hacks, but after spending about half a day on this seemingly very simple layout issue, I just gave up and went with it. >.<

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

Alternatively you could also call reloadData it in viewDidAppear (without the DispatchQueue hack), but then you can clearly see the "jump" when the layout jumps from "incorrect" to "correct".

Anyway, just wanted to share my experience and hope this helps someone else. Cheers!

2
votes

I had your problem too. Instead of remove

tableView.estimatedRowHeight = 70

I just added a layoutIfNeeded at the end of the cellForRow method, just before return the cell itself:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as? MyCustomCell
    ...
    cell?.layoutIfNeeded()
    return cell!
}

Result: the cell layout is perfect always, the first time and every after reuse.

0
votes

In my case, the issue was caused by estimatedRowHeight.

Simply removing this line

tableView.estimatedRowHeight = 70

fixed my problems. Cell properly updated its layout and it almost fixed my issues.

But, most likely, you’re going to get another trouble, with your cell’s height being set to 43.5 points. You log will also be filled with auto layout errors, that will include a line like this

<NSLayoutConstraint:0x600000097570 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fd4ee511d20.height == 43.5   (active)>

Apparently, if you won’t provide estimatedRowHeight, table view puts a 43.5 points height constraint on your cell’s content view, and if your cell’s “internal” height will not match (and probability of that is 99.99%), then it’s going to put errors in log.

How to avoid that error? I don’t know yet. I post a question about that, and as soon as I find an answer, I will provide a link in this question.