UICollectionViewFlowLayout crash on delete due to self sizing cells

Originator:kellerbryan19
Number:rdar://41101885 Date Originated:06/16/2018
Status:Closed Resolved:Yes
Product:UIKit Product Version:iOS 11
Classification:Crash Reproducible:Yes
 
Summary:
UICollectionViewFlowLayout crashes in certain scenarios when items are being removed from the collection view. 

This happens when:
1.  There is an unsized item (has not received preferred layout attributes) just below the bottom of the screen.
2. An on-screen item is removed, causing the unsized item to slide up into the visible region
3. UICollectionViewFlowLayout receives a call to [UICollectionViewFlowLayout invalidationContextForPreferredLayoutAttributes:withOriginalAttributes:], with a final size for the item that is sliding up onto the screen due to the item removal
4. The index path in the preferred layout attributed *is the old index before the deletion*
5. [_UIFlowLayoutInfo setSize:forItemAtIndexPath:], then [_UIFlowLayoutSection setSize:forItemAtIndexPath:invalidationContext:] are invoked with the final size for the item
6. [_UIFlowLayoutSection setSize:forItemAtIndexPath:invalidationContext:] indexes into its array of _UIFlowLayoutItems, but with the index of the item *before the batch update*, which results in an index out of bounds crash since the item has already been removed from the array of items.


My layout has a similar concept of "model state" (_UIFlowLayoutInfo), "section models" (_UIFlowLayoutSection), and "item models" (_UIFlowLayoutItem). The way we've modeled out layouts is eerily similar actually, which has been quite entertaining to me as I've peeled back parts of UICollectionView and UICollectionViewFlowLayout during my bug investigations :)

I solve this by saving off the "previous" section models as soon as my model state receives some batch updates, that way I can always reference the "old" state and the "new" state as batch updates are occurring. Previous state is cleared up once finalizeBatchUpdates is called. I understand that this may not be an option for flow layout, since that would nearly double memory usage, although very briefly, while batch updates are in flight. Overall, this model has allowed me to maintain a very clean architecture, and allowed me to avoid this crash in my layout.

Steps to Reproduce:
1. Open sample project and deploy (I tested on iPhone X simulator, but others should work fine)
2. The collection view is initially empty. Click append 3 times. The first item will be fully visible. The second item will be partially visible. The third item will be completely off screen.
3. Click remove. This removes the first item (index 0) and causes item 2 (index 1) to become completely visible), and item 3 (index 2) to become partially visible.

Observe crash. If you trace through the code, you'll see that when item 3 (index 2) starts to slide up, it receives preferred layout attributes (as described in Summary)


Link to sample project: https://drive.google.com/file/d/1HEDsWoU1xWd2PnpCRttlCo2rm3Iz1kGp/view?usp=sharing

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!