UITableViewCell layout not updating until cell is reused

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.

You can find the sample project with the bug here: https://www.dropbox.com/s/vqg0gw0a8ycziyq/ReusedCellBug.zip?dl=0

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


P.S. After playing around I found out that if I completely remove `tableView.estimatedRowHeight = 70`, then layout works as expected, but trying to use autosizing cells without

estimatedRowHeight
leads to table view putting height constraint on cell's content view (height equals to 43.5 point). In error logs this constraint is the famous
'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fd63d508490.height == 43.5 (active)

And because my cell's height is not equal to 43.5 points, auto layout decides that this height constraint is more important than my cell's layout and fits my cell content in 43.5 points.

So I can't use

estimatedRowHeight
because it breaks auto layout, but without it my layout also gets broken.

how can I safely deprioritize this

UIView-Encapsulated-Layout-Height
constraint? Is there any way I can get autosizing cells without using
estimatedRowHeight
?
Post not yet marked as solved Up vote post of xinatanil Down vote post of xinatanil
19k views

Replies

I realize this post is over a year old, but I'm running into something similar. Did you )or anyone) find a fix for this?

xinatanil >> Cell layout is not updated until the cell is reused.


If this is the behavior you're seeing too, the question is: when is that, exactly? Reuse seems like the correct time for auto-layout to occur. Ideally, it would happen after you return a cell from "tableView(_:cellForRowAt:)", but if it happened at dequeuing that doesn't sound too bad (so long as "setNeedsLayout()" could legally be issued then).


Maybe you can show some fragments of code?

Did you find a solution to this? Cheers

Try to do what you need in draw function of UITableViewCell. It calls when cell created

Try to scroll tableview to top in ViewController after reloadData of tableView tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)

Cell layout is not updated until cell is reused.

if you want tableview to reflect the changed constraints of cells.

one solution is to reload the cells whose layout constraints have been updated using tableview.reloadRows(at: [IndexPath], with: animation)

other solution is

tableview.beginUpdates()
tableview.setNeedsDisplay()
tableview.endUpdates()

Calling cell.layoutIfNeeded() before returning from tableView(_:, cellForRowAt indexPath:) forces resizing its internal elements and its height will be calculated correctly on the first usage.

This shouldn't always be done but solves cases where there could be some delayed calculation going on for the next run loop.

Try this. it worked for me.

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

Try this. it worked for me.

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

I had the same issue, but then I realised that I forget to call super within my 'override func prepareForReuse() {...}' method. Try this.