Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash when invalidating then inserting/deleting #13

Open
JosephDuffy opened this issue Sep 22, 2020 · 2 comments
Open

Crash when invalidating then inserting/deleting #13

JosephDuffy opened this issue Sep 22, 2020 · 2 comments
Labels
bug Something isn't working

Comments

@JosephDuffy
Copy link
Member

Describe the bug

A crash can occur when using invalidateAll followed by an update (I've only triggered this with an insert but others could work, I suspect delete too).

To Reproduce

Call invalidateAll, followed by an insert/delete. I think the invalidateAll call needs to trigger a layout.

Expected behavior

It should not crash.

Environment

  • OS Version: iOS 13.7
  • Library Version: master (8ee5b11, 1.0.3 + some fixes)
  • Device: iPod Touch

Additional context

I think the issue comes down to how the API for the delegate and UICollectionView itself differ. My understanding is that calling reloadData (which is done on an invalidate) does not guarantee that the changes have been fully applied after reloadData occurs, e.g. a layout pass might be required.

When looking at the docs for performBatchUpdates it states:

If the collection view's layout is not up to date before you call this method, a reload may occur. To avoid problems, you should update your data model inside the updates block or ensure the layout is updated before you call performBatchUpdates(_:completion:).

This would be ok if we were updating the data model inside the updates block, but updates are surrounded by willBeginUpdating and didEndUpdating. didEndUpdating triggers the call to performBatchUpdates but by this point the models aren't in-sync with what the collection view thinks is true.

So I think when we call reloadData and then performBatchUpdates before the layout has occurred the collection view will try to update straight away. This starts with numberOfSections which will return the new value, but when the collection view then requires for a cell it will eventually hit elementsProvider(for:) which will crash because the cachedProviders has not been updated yet because the prepareSections call occurs inside the performBatchUpdates.

I tried calling layoutIfNeeded before performBatchUpdates. This essentially triggers the same crash since I think that's what performBatchUpdates is doing internally

I also tried calling collectionView.layoutIfNeeded() straight after collectionView.reloadData() which fixes the crash but seems to cause a visual bug so I'm not sure if this can be classed as a fix or not.

Ultimately the fix for me was to remove invalidateAll and do diffing, but since most of the built-in types use invalidateAll this isn't really a fix either.

Ultimately I think the fix is to update the delegate in such a way that the model updates can occur inside the performBatchUpdates call, but this would need some more thought an a major version bump.

@JosephDuffy JosephDuffy added the bug Something isn't working label Sep 22, 2020
@shaps80
Copy link
Collaborator

shaps80 commented Sep 28, 2020

Major version bump why? Otherwise I can see the issue.

@JosephDuffy
Copy link
Member Author

Major version bump why? Otherwise I can see the issue.

I don't see a way of fixing this with the existing API so it would have to replace the existing delegate (major version bump) or a new delegate be introduced and this issue be documented.

JosephDuffy added a commit that referenced this issue Nov 3, 2020
As mentioned in #13 and #8 there are some scenarios where the collection view’s data is out-of-sync with the data in composed.

As mentioned in #13 calling `layoutIfNeeded` can trigger the data to be in sync again. In this I have added it to `mappingWillBeginUpdating(_:)` which _appears_ to solve the problem.

It might be needed in `replace(sectionProvider:)` (because `reloadData` is called) and/or `mappingDidInvalidate(_:)` (for the same reason) but I’m still investigating.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Development

No branches or pull requests

2 participants