13

My app has two CollectionViewControllers. Only one is visible at a given time.

I have created the following structure on storyboard: two container views on top of each other. Every container view has a CollectionViewController embedded. The visibility of a particular container view determines which collectionViewController is visible.

This is the problem. Both CollectionViewControllers are receiving data in parallel but iOS has a bug that will make the app crash if one CollectionViewController tries to execute an insert using performBatchUpdates while it is invisible.

Trying to prevent that, I have created a BOOL flag on both CollectionViewControllers so they can know if they are visible and execute or not the performBatchUpdates. Something like:

if (self.isThisCollectionViewVisible == NO) return;

[self.collectionView performBatchUpdates:^{
   // bla bla... perform insert,m remove...

This solves part of the problem. But the app continues to crash on the following condition: if I tap the button to switch to the invisible CollectionViewController making it visible while it is receiving updates.

I mean this: lets call A the first CollectionViewController and B the second one. A is visible and B is invisible at this point. B starts receiving data and is trying to do a performBatchUpdates but as it is invisible, the if (self.isThisCollectionViewVisible == NO) return; is preventing performBatchUpdates to run, what is fine. Now I make A invisible and B visible. At this point the flag self.isThisCollectionViewVisible is set to YES and performBatchUpdates makes the app crash with this error:

* Assertion failure in -[CollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.7/UICollectionView.m:4625 * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (76) must be equal to the number of items contained in that section before the update (70), plus or minus the number of items inserted or deleted from that section (5 inserted, 2 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'

I think the CollectionViewController is really not yet ready and updated to be able to do a performBatchUpdates... and this is not a matter of not updating the data source previously because it is being updated.

What checks can I do to prevent that from happening?

NOTE: I noticed something strange about this crash in particular. It says that 5 elements are being inserted and 2 deleted but in fact 3 elements are being inserted, 0 deleted and 2 changed when the crashes happen.

Duck
  • 34,902
  • 47
  • 248
  • 470
  • You mentioned that iOS has a bug related to performBatchUpdates. How did you determine this? Are you sure the issue is not with your code? Using a flag to check for visible/invisible seems likely to be unreliable. – Mike Taverne Mar 31 '16 at 14:43
  • I found a bunch of radar entries related to bugs of performBatchUpdates but anyway, in this case I am not using the flag anymore. That was just a try to see where the problem was. – Duck Mar 31 '16 at 16:43

3 Answers3

21

For me adding self.collectionView.numberOfItemsInSection(0) fixed the crash. The collectionView has issues while inserting items when it is not visible.

Seems like I'm not alone with my solution: http://www.openradar.me/15262692

João Nunes
  • 3,751
  • 31
  • 32
3

This crash told you that you didn't updated your datasource for collection. You need to update your dataSource (array or dictionary) and reload collection view data after you perform performBatchUpdates.

Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (76) must be equal to the number of items contained in that section before the update (70), plus or minus the number of items inserted or deleted from that section (5 inserted, 2 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'

As written in apple docs

Deletes are processed before inserts in batch operations. This means the indexes for the deletions are processed relative to the indexes of the collection view’s state before the batch operation, and the indexes for the insertions are processed relative to the indexes of the state after all the deletions in the batch operation.

So, move the changes before the inserts and it will the trick!

Duck
  • 34,902
  • 47
  • 248
  • 470
Karaban
  • 151
  • 8
  • unfortunately as far as I have verified one hundred times, I am updating the datasource, what is very strange. – Duck Mar 31 '16 at 13:36
  • Deletes are processed before inserts in batch operations. This means the indexes for the deletions are processed relative to the indexes of the collection view’s state before the batch operation, and the indexes for the insertions are processed relative to the indexes of the state after all the deletions in the batch operation. – Karaban Mar 31 '16 at 13:46
  • so be sure to delete before insert in your block of performBatchUpdate – Karaban Mar 31 '16 at 13:47
  • did that now. Still crashing. – Duck Mar 31 '16 at 13:50
  • bad news) ok last trick) clean all data source (in example, removeAllObjects of NSMutableArray) and in - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion in completion populate it – Karaban Mar 31 '16 at 13:59
  • 1
    Inside `performBatchUpdates` I was performing inserts, removals and changes in that order. You told me to put delete before insert. I did that and it continued to crash but then I moved the changes before the inserts and it did the trick. Now it is not crashing. Please add this update to your answer so I can accept it. THANKS!!!!! – Duck Mar 31 '16 at 14:34
  • I updated my answer. Thanks for your question (it was challenge to discover the problem) and comments. – Karaban Apr 01 '16 at 08:05
1

Encountered the same error today, for me, in performBatchUpdates block replace this:

  NSArray *selectedItemsIndexPaths = [self.collectionView indexPathsForSelectedItems];

with this:

  NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:self.selectIndex inSection:0];
  NSArray *selectedItemsIndexPaths = @[selectedIndexPath];

Maintain the index by myself, it's OK now. The error should not be associated with data source, if you have had update the data source. It maybe related to the reuse of cells.

Hongli Yu
  • 368
  • 3
  • 7