insertItemsAtIndexPaths throws NSInternalInconsistencyException when called after UICollectionView reloadData.

Originator:anthony
Number:rdar://27941923 Date Originated:8/21/2016
Status:Open Resolved:
Product:iOS Product Version:9.3.2
Classification:Bug Reproducible:Always
 
Summary:

Looks like a more general case of rdar://26484150

Consider the following snippet:

- (void)whateverMethod {
  [_collectionView reloadData];
  _numberOfItems += 1;
  [_collectionView insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:0] ]];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView
     numberOfItemsInSection:(NSInteger)section {
  return _numberOfItems;
}

insertItemsAtIndexPaths: throws NSInternalInconsistencyException.

The reason is reloadData effectively does nothing, but just invalidates some internal collection view's state that will trigger data source methods when it's considered inevitable, but never earlier.

Since collection view's state is already invalid (meaning it knows nothing about the current numberOfItems after reloadData) by the time we call insertItemsAtIndexPath, the latter must ask the data source twice for the numberOfItemsInSection:. First time it expects to get the number of items before the insertions, and the second time - after. We've already modified the model at that point to a new number of items, so it gets same number each time and throws NSInconsistencyException.

The easiest way to fix such cases would be wrapping the model modifications and corresponding insert/deleteItemsAtIndexPath with performBatchUpdates: even when we technically don’t do multiple updates.
That causes the collection view to get the current number of items synchronously from the data source before executing the updates block. Like this:

[_collectionView performBatchUpdates:^{
  _numberOfItems += 1;
  [_collectionView insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:0] ]];
}
animated:YES];

Expected Results:
UICollectionView performs the insertion and doesn't throw NSInternalInconsistencyException.
OR the official documentation describes this issue and provides some recommendations how to bypass it.

Actual Results:
2016-08-21 18:52:37.381 iOS Polygon[22971:15295480] *** Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UICollectionView.m:4625
2016-08-21 18:52:37.386 iOS Polygon[22971:15295480] *** 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 (11) must be equal to the number of items contained in that section before the update (11), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
*** First throw call stack:
(
	0   CoreFoundation                      0x0000000102236d85 __exceptionPreprocess + 165
	1   libobjc.A.dylib                     0x0000000101ca8deb objc_exception_throw + 48
	2   CoreFoundation                      0x0000000102236bea +[NSException raise:format:arguments:] + 106
	3   Foundation                          0x00000001018f1d5a -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 198
	4   UIKit                               0x0000000102e7c077 -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:] + 15363
	5   UIKit                               0x0000000102e782ca -[UICollectionView _updateRowsAtIndexPaths:updateAction:] + 350
	6   iOS Polygon                         0x00000001017a273a -[ViewController viewWillTransitionToSize:withTransitionCoordinator:] + 314
	7   UIKit                               0x0000000102797bd9 +[UIViewController _performWithoutDeferringTransitions:] + 110
	8   UIKit                               0x00000001027b06e2 -[UIViewController(AdaptiveSizing) _window:viewWillTransitionToSize:withTransitionCoordinator:] + 1059
	9   UIKit                               0x0000000102666ef7 __59-[UIWindow _rotateToBounds:withAnimator:transitionContext:]_block_invoke + 175
	10  UIKit                               0x0000000102666d16 -[UIWindow _rotateToBounds:withAnimator:transitionContext:] + 424
	11  UIKit                               0x0000000102669c54 -[UIWindow _rotateWindowToOrientation:updateStatusBar:duration:skipCallbacks:] + 2047
	12  UIKit                               0x000000010266a74d -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 842
	13  UIKit                               0x0000000102669280 -[UIWindow _setRotatableViewOrientation:updateStatusBar:duration:force:] + 184
	14  UIKit                               0x0000000102667ed1 __57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 107
	15  UIKit                               0x0000000102667d09 -[UIWindow _updateToInterfaceOrientation:duration:force:] + 486
	16  CoreFoundation                      0x0000000102200c8c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
	17  CoreFoundation                      0x00000001022009cb _CFXRegistrationPost + 427
	18  CoreFoundation                      0x0000000102200732 ___CFXNotificationPost_block_invoke + 50
	19  CoreFoundation                      0x00000001022491e2 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1986
	20  CoreFoundation                      0x00000001020f8679 _CFXNotificationPost + 633
	21  Foundation                          0x0000000101833cd9 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
	22  UIKit                               0x000000010293830c -[UIDevice setOrientation:animated:] + 326
	23  UIKit                               0x000000010260e96f __54-[UIApplication _handleDeviceOrientationChangedEvent:]_block_invoke + 87
	24  UIKit                               0x000000010260e8e8 -[UIApplication _handleDeviceOrientationChangedEvent:] + 124
	25  UIKit                               0x000000010260e7f8 -[UIApplication handleEvent:withNewEvent:] + 1672
	26  UIKit                               0x000000010260ed17 -[UIApplication sendEvent:] + 88
	27  UIKit                               0x00000001026013e5 _UIApplicationHandleEvent + 476
	28  GraphicsServices                    0x0000000105af41cb _PurpleEventCallback + 749
	29  GraphicsServices                    0x0000000105af3cd3 PurpleEventCallback + 35
	30  CoreFoundation                      0x000000010215c579 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
	31  CoreFoundation                      0x000000010215c4e9 __CFRunLoopDoSource1 + 473
	32  CoreFoundation                      0x0000000102151c60 __CFRunLoopRun + 2272
	33  CoreFoundation                      0x00000001021510f8 CFRunLoopRunSpecific + 488
	34  GraphicsServices                    0x0000000105af2ad2 GSEventRunModal + 161
	35  UIKit                               0x00000001025edf09 UIApplicationMain + 171
	36  iOS Polygon                         0x00000001017a2a0f main + 111
	37  libdyld.dylib                       0x0000000104a2892d start + 1
	38  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

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!