UICollectionView: how to use the new iOS 13 API UICollectionViewDiffableDataSource & NSDiffableDataSourceSnapshot to have a paginated collection?

I'm trying to use the new UICollectionView API: UICollectionViewDiffableDataSource & NSDiffableDataSourceSnapshot, simulating a network offset based API. My goal is to have a paginated collection.

Problem: When I call the loadMore scrolling I get this crash 
self.presentingView?.dataSource.apply(snapshot, animatingDifferences: true, completion: nil)


2020-09-25 11:54:50.462562+0200 TestCollectionView[3518:152267] *** Assertion failure in NSArray<UICollectionViewUpdateItem *> * Nonnull UIDiffableDataSourceApplyInsertUpdate(NSObject<UIDiffableDataSourceUpdate> *strong Nonnull, NSMutableOrderedSet *strong Nonnull, NSMutableOrderedSet *strong Nonnull, UIDataSourceSnapshotter *strong Nonnull, BOOL)(), UIDiffableDataSourceHelpers.m:512 2020-09-25 11:54:50.472593+0200 TestCollectionView[3518:152267] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: index != NSNotFound' * First throw call stack: ( 0 CoreFoundation 0x00007fff2043a126 exceptionPreprocess + 242 1 libobjc.A.dylib 0x00007fff20177f78 objcexceptionthrow + 48 2 CoreFoundation 0x00007fff20439f4f +[NSException raise:format:] + 0 3 Foundation 0x00007fff20788293 -[NSAssertionHandler handleFailureInFunction:file:lineNumber:description:] + 166 4 UIKitCore 0x00007fff24b1c9b0 UIDiffableDataSourceApplyInsertUpdate + 1793 5 UIKitCore 0x00007fff23d0b52a -[UIDiffableDataSource commitUpdate:snapshot:completion:] + 323 6 UIKitCore 0x00007fff23d0af44 113-[UIDiffableDataSource applyDifferencesFromSnapshot:viewPropertyAnimator:customAnimationsProvider:completion:]blockinvoke.228 + 182 7 UIKitCore 0x00007fff23d0b007 113-[UIDiffableDataSource applyDifferencesFromSnapshot:viewPropertyAnimator:customAnimationsProvider:completion:]blockinvoke.238 + 64 8 libdispatch.dylib 0x0000000101837a88 dispatchclientcallout + 8 9 libdispatch.dylib 0x0000000101846cac dispatchlanebarriersyncinvokeandcomplete + 132 10 UIKitCore 0x00007fff23d0a969 -[UIDiffableDataSource applyDifferencesFromSnapshot:viewPropertyAnimator:customAnimationsProvider:completion:] + 1211 11 UIKitCore 0x00007fff23d09874 -[UIDiffableDataSource applyDifferencesFromSnapshot:completion:] + 55 12 UIKitCore 0x00007fff23d09f25 -[UIDiffableDataSource applyDifferencesFromSnapshot:animatingDifferences:completion:] + 71 13 libswiftUIKit.dylib 0x00007fff539f5a56 $s5UIKit34UICollectionViewDiffableDataSourceC5apply20animatingDifferences10completionyAA010NSDiffableeF8SnapshotVyxqGSbyycSgtFTm + 230 14 TestCollectionView 0x00000001015645cd $s18TestCollectionView0B9PresenterC21fetchNextPageIfNeededyyFys6ResultOyAA5ModelVs5ErrorpGcfU + 1101 15 TestCollectionView 0x0000000101564031 $s18TestCollectionView0B12DataProviderC08retrieveD06offset5limit10completionySiSiys6ResultOyAA5ModelVs5ErrorpGctF + 753 16 TestCollectionView 0x0000000101564160 $s18TestCollectionView0B9PresenterC21fetchNextPageIfNeededyyF + 160 17 TestCollectionView 0x000000010156331c $s18TestCollectionView0bC10ControllerC8loadMore10completionyySbctF + 92 18 TestCollectionView 0x0000000101563379 $s18TestCollectionView0bC10ControllerCAA33VerticalPaginationManagerDelegateA2aDP8loadMore10completionyySbctFTW + 9 19 TestCollectionView 0x000000010155c283 $s18TestCollectionView25VerticalPaginationManagerC16setContentOffSet337A1CA207B09278E8BCBA7248F7151A31LLyySo7CGPointVF + 1187 20 TestCollectionView 0x000000010155bcf9 $s18TestCollectionView25VerticalPaginationManagerC12observeValue10forKeyPath2of6change7contextySSSgypSgSDySo05NSKeyh6ChangeJ0aypGSgSvSgtF + 1625 21 TestCollectionView 0x000000010155c506 $s18TestCollectionView25VerticalPaginationManagerC12observeValue10forKeyPath2of6change7contextySSSgypSgSDySo05NSKeyh6ChangeJ0aypGSgSvSgtFTo + 582 22 Foundation 0x00007fff207d6124 NSKeyValueNotifyObserver + 329 23 Foundation 0x00007fff207d9858 NSKeyValueDidChange + 439 24 Foundation 0x00007fff207d91d8 -[NSObject(NSKeyValueObservingPrivate) changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 741 25 Foundation 0x00007fff207d9a3b -[NSObject(NSKeyValueObservingPrivate) changeValueForKey:key:key:usingBlock:] + 68 26 Foundation 0x00007fff207d37db NSSetPointValueAndNotify + 304 27 UIKitCore 0x00007fff24b2deaa -[UIScrollView updatePanGesture] + 4833 28 UIKitCore 0x00007fff2419bee7 -[UIGestureRecognizerTarget sendActionWithGestureRecognizer:] + 49 29 UIKitCore 0x00007fff241a6168 UIGestureRecognizerSendTargetActions + 100 30 UIKitCore 0x00007fff241a2a2f UIGestureRecognizerSendActions + 294 31 UIKitCore 0x00007fff241a1d8e -[UIGestureRecognizer updateGestureForActiveEvents] + 725 32 UIKitCore 0x00007fff24194352 UIGestureEnvironmentUpdate + 2652 33 UIKitCore 0x00007fff2419347a -[UIGestureEnvironment updateForEvent:window:] + 887 34 UIKitCore 0x00007fff246a582d -[UIWindow sendEvent:] + 4752 35 UIKitCore 0x00007fff2467f376 -[UIApplication sendEvent:] + 633 36 UIKitCore 0x00007fff2470f8d6 processEventQueue + 13895 37 UIKitCore 0x00007fff2470626c eventFetcherSourceCallback + 104 38 CoreFoundation 0x00007fff203a8845 CFRUNLOOPISCALLINGOUTTOASOURCE0PERFORMFUNCTION + 17 39 CoreFoundation 0x00007fff203a873d CFRunLoopDoSource0 + 180 40 CoreFoundation 0x00007fff203a7c1f CFRunLoopDoSources0 + 248 41 CoreFoundation 0x00007fff203a23f7 CFRunLoopRun + 878 42 CoreFoundation 0x00007fff203a1b9e CFRunLoopRunSpecific + 567 43 GraphicsServices 0x00007fff2b773db3 GSEventRunModal + 139 44 UIKitCore 0x00007fff24660af3 -[UIApplication _run] + 912 45 UIKitCore 0x00007fff24665a04 UIApplicationMain + 101 46 TestCollectionView 0x00000001015709db main + 75 47 libdyld.dylib 0x00007fff20257415 start + 1 ) libcabi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: index != NSNotFound' terminating with uncaught exception of type NSException

Implementation






Root cause
Code Block
func fetchNextPageIfNeeded() {
dataProvider.retrieveData(offset: currentOffset, limit: 10) { result in
switch result {
case .success(let data):
self.updateOffset(data.next)
if var snapshot = self.presentingView?.dataSource.snapshot() {
snapshot.appendItems(data.words, toSection: .main)
self.presentingView?.dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
}
case .failure(_):
break
}
}
}