Modifying the view hierarchy of a UITableViewCell as part of reloadData

I am trying to debug a crash due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSFrozenDictionary layoutSublayers]: unrecognized selector sent to instance.

I see 2 things that I find interesting about it.

  • The fact that the instance is a __NSFrozenDictionary tells me that the reference to a CALayer that has since been evicted from memory and re-written.
  • The call to layoutSublayers tells me that the CALayer was dealloc-ed at some point between the call to setNeedsLayout (or layoutIfNeeded)

This seemingly occurs as part of a call to -[UITableView reloadData]

Furthermore, each cell created by the UITableView has a UIStackView.

  • As part of the call to cellForRowAtIndexPath the code adds an instance of "custom view" to the stack view's subviews.
  • As part of the call to prepareForReuse the code removes the "custom view" from the stack view's subviews.

Therefore as part of the prepareForReuse the "custom view" (and its layer) is evicted from memory.

My theory is that the tableview does a layout pass on the visible cell which has since had its subview removed which causes the crash.

My question is what are the constraints on when/where to call reloadData and/or when/where you should definitely avoid it as it relates to this context?

This is code that modifies the view hierarchy of the cell as part of its lifecycle which AFAIK this is "not supported" since prepareForReuse is meant to be used for resetting view state and cellForRowAtIndexPath to reset content.

In that sense, another question, are you not allowed to modify the cell view hierarchy period as part of the cycle to draw the visible cells or is it more of a case, do not call reloadData?

Answered by DTS Engineer in 794869022

reloadData throw all of the views/cells away, and re-create everything. UITableView cannot track the identity of sections or items across reloadData, it’s a complete reset.

So general you don’t want to call reloadData if you can help it. Ideally you'll want to use a combination of UITableViewDiffableDataSource which handles automatically diffing sections & items for you, and generating the correct batch updates on the UITableView) + your own change detection for properties within existing items so you can call the reconfigure or reload APIs on the diffable snapshot as needed, to update content within existing views.

reloadData throw all of the views/cells away, and re-create everything. UITableView cannot track the identity of sections or items across reloadData, it’s a complete reset.

So general you don’t want to call reloadData if you can help it. Ideally you'll want to use a combination of UITableViewDiffableDataSource which handles automatically diffing sections & items for you, and generating the correct batch updates on the UITableView) + your own change detection for properties within existing items so you can call the reconfigure or reload APIs on the diffable snapshot as needed, to update content within existing views.

Can you please provide some clarity on the following?

Is your code allowed to modify the view hierarchy of a cell as part of the prepareForReuse, cellForRowAtIndexPath dance? Or is it unsupported by UIKit, thus will lead to unexpected behaviour?

If it's unsupported, is the proposed solution to create and register a distinct cell for each data that is significantly different to warrant a new cell definition? Is there another way?

Modifying the view hierarchy of a UITableViewCell as part of reloadData
 
 
Q