0

I am using diffable data sources in a UICollectionView using a layout of UICollectionViewCompositionalLayout.list. My collection view supports reordering of cells via reorderingHandlers on the UICollectionViewDiffableDataSource. Additionally, I am mixing usage of NSDiffableDataSourceSnapshot and NSDiffableDataSourceSectionSnapshot, as is permitted in the documentation:

You can use section snapshots with or instead of an NSDiffableDataSourceSnapshot

I have noticed a strange error when I reorder cells after applying a NSDiffableDataSourceSnapshot followed by a NSDiffableDataSourceSectionSnapshot.

An excerpt from the UIViewController is shown below:

override func viewDidLoad() {
    super.viewDidLoad()

    // Omitted the code to build the UICollectionView, add to the view, and assign its dataSource.

    // Build and apply the initial snapshot, as a NSDiffableDataSourceSnapshot - NOT 
    // as a set of NSDiffableDataSourceSectionSnapshots.
    // For this example, we just use some arbitrary test data.
    var initialSnapshot = NSDiffableDataSourceSnapshot<Section, UUID>()
    for section in Section.allCases {
        initialSnapshot.appendSections([section])
        initialSnapshot.appendItems([UUID(), UUID(), UUID()], toSection: section)
    }
    dataSource.apply(initialSnapshot, animatingDifferences: false)

    // Then apply a section snapshot (same data as the initial snapshot).
    var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<UUID>()
    sectionSnapshot.append(initialSnapshot.itemIdentifiers(inSection: .sectionTwo))
    self.dataSource.apply(sectionSnapshot, to: .sectionTwo)
}

My data source is defined as:

private lazy var dataSource = makeDataSource()

func makeDataSource() -> UICollectionViewDiffableDataSource<Section, UUID> {
    let cellRegistration = makeCellRegistration()
    let dataSource = UICollectionViewDiffableDataSource<Section, UUID>(collectionView: collectionView) { view, indexPath, item in
        view.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
    }
    dataSource.reorderingHandlers.canReorderItem = { _ in true }
    return dataSource
}

The full contents of my minimum reproducible example are contained in a small sample project. This includes the cell registration, building the UICollectionView, adding it to the view, etc.

When I reorder any cells in a section other than the one that had the section snapshot applied, I get an assertion failure:

Assertion failure in -[NSDiffableDataSourceSectionSnapshot snapshotOfParentItem:includingParentItem:], NSDiffableDataSourceSectionSnapshot.m:330

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: index != NSNotFound'

The call stack, shown below, suggests that this is occurring within the framework in preparation of producing the post-reorder snapshot.

*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff203fbbb4 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007fff2019ebe7 objc_exception_throw + 48
    2   Foundation                          0x00007fff20750c12 _userInfoForFileAndLine + 0
    3   UIKitCore                           0x00007fff24f97699 -[NSDiffableDataSourceSectionSnapshot snapshotOfParentItem:includingParentItem:] + 755
    4   UIKitCore                           0x00007fff24340b8c -[_UIDiffableDataSourceSectionSnapshotRebaser _rebaseForInitialSnapshot:finalSnapshot:initialSectionSnapshots:dataSourceDiffer:shouldPerformChildSnapshotMoves:] + 2368
    5   UIKitCore                           0x00007fff24340218 -[_UIDiffableDataSourceSectionSnapshotRebaser initWithInitialSnapshot:finalSnapshot:initialSectionSnapshots:dataSourceDiffer:shouldPerformChildSnapshotMoves:] + 226
    6   UIKitCore                           0x00007fff24f23c67 +[NSDiffableDataSourceTransaction _computeReorderingTransactionWithInitialSnapshot:reorderingUpdate:sectionSnapshotProvider:] + 2051
    7   UIKitCore                           0x00007fff24607f54 -[__UIDiffableDataSource _reorderingTransactionForReorderingUpdate:] + 187
    8   UIKitCore                           0x00007fff24607c42 -[__UIDiffableDataSource _commitReorderingForItemAtIndexPath:toDestinationIndexPath:shouldPerformViewAnimations:] + 136
    9   libswiftUIKit.dylib                 0x00007fff59345148 $s5UIKit34UICollectionViewDiffableDataSourceC010collectionC0_10moveItemAt2toySo0bC0C_10Foundation9IndexPathVAKtFTf4dnnn_n + 72
    10  libswiftUIKit.dylib                 0x00007fff5933ef65 $s5UIKit34UICollectionViewDiffableDataSourceC010collectionC0_10moveItemAt2toySo0bC0C_10Foundation9IndexPathVAKtFTo + 165
    11  UIKitCore                           0x00007fff2468137c -[UICollectionView _notifyDataSourceForMoveOfItemFromIndexPath:toIndexPath:] + 273
    12  UIKitCore                           0x00007fff246800cd -[UICollectionView _completeInteractiveMovementWithDisposition:completion:] + 1686
    13  UIKitCore                           0x00007fff2461be1c -[UICollectionViewListCell _reorderControlDidEndReordering:cancelled:] + 85
    14  UIKitCore                           0x00007fff2461e92b -[_UICollectionViewListCellReorderControl endReordering:] + 64
    15  UIKitCore                           0x00007fff2461e5bc -[_UICollectionViewListCellReorderControl pan:] + 414
    16  UIKitCore                           0x00007fff24ad0f8f -[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 49
    17  UIKitCore                           0x00007fff24adb0e9 _UIGestureRecognizerSendTargetActions + 100
    18  UIKitCore                           0x00007fff24ad7c55 _UIGestureRecognizerSendActions + 294
    19  UIKitCore                           0x00007fff24ad6f91 -[UIGestureRecognizer _updateGestureForActiveEvents] + 725
    20  UIKitCore                           0x00007fff24ac9213 _UIGestureEnvironmentUpdate + 2713
    21  UIKitCore                           0x00007fff24ac82f2 -[UIGestureEnvironment _updateForEvent:window:] + 902
    22  UIKitCore                           0x00007fff250449c9 -[UIWindow sendEvent:] + 5273
    23  UIKitCore                           0x00007fff2501b4e8 -[UIApplication sendEvent:] + 825
    24  UIKitCore                           0x00007fff250b128a __dispatchPreprocessedEventFromEventQueue + 8695
    25  UIKitCore                           0x00007fff250b3a10 __processEventQueue + 8579
    26  UIKitCore                           0x00007fff250aa1b6 __eventFetcherSourceCallback + 240
    27  CoreFoundation                      0x00007fff20369e25 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    28  CoreFoundation                      0x00007fff20369d1d __CFRunLoopDoSource0 + 180
    29  CoreFoundation                      0x00007fff203691f2 __CFRunLoopDoSources0 + 242
    30  CoreFoundation                      0x00007fff20363951 __CFRunLoopRun + 875
    31  CoreFoundation                      0x00007fff20363103 CFRunLoopRunSpecific + 567
    32  GraphicsServices                    0x00007fff2c851cd3 GSEventRunModal + 139
    33  UIKitCore                           0x00007fff24ffbe63 -[UIApplication _run] + 928
    34  UIKitCore                           0x00007fff25000a53 UIApplicationMain + 101
    35  libswiftUIKit.dylib                 0x00007fff5933d052 $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 98
    36  diffable-experiments                0x0000000100759fe8 $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 104
    37  diffable-experiments                0x0000000100759f77 $s20diffable_experiments11AppDelegateC5$mainyyFZ + 39
    38  diffable-experiments                0x000000010075a068 main + 24
    39  dyld                                0x0000000100788e1e start_sim + 10
    40  ???                                 0x0000000000000001 0x0 + 1

Am I doing anything wrong? Is there a workaround?

Andrew Bennet
  • 2,600
  • 1
  • 21
  • 55
  • Could you at least show us _some_ code here as part of the question? – matt Sep 29 '21 at 23:12
  • I've updated the question. I wasn't sure which part of the code would be key, and the full example is 93 lines (which I think is probably too long for the question) - but I've now added the code for the creation of the datasource and the reorderHandler which is _probably_ the most likely to be key. – Andrew Bennet Sep 30 '21 at 08:10
  • Since found a way to simplify the example; updated the sample project and the code here. I don't even need any didReorder handlers defined to trigger the error. I own a few of your books by the way! Programming iOS 9 was my first iOS book :) – Andrew Bennet Sep 30 '21 at 08:17
  • Did you ever figure this out? – Alex Walczak Aug 21 '23 at 04:30
  • It was acknoledged as an issue by Apple in the response to my Feedback: see my answer I've just posted. – Andrew Bennet Aug 21 '23 at 09:31

1 Answers1

1

This issue was raised via Feedback Assistant (FB9662195), and acknowledged as an issue by Apple. The response:

Thank you for providing the sample project. We have identified a workaround that you can use for this issue. Instead of populating both the sections and items using the top-level NSDiffableDataSourceSnapshot initially, only set up the sections using the top-level NSDiffableDataSourceSnapshot, and then apply section snapshots (NSDiffableDataSourceSectionSnapshot) to populate the items in each section.

I ended up working around this by just not using NSDiffableDataSourceSectionSnapshot. A shame.

Note that the exception message changed at some point, in iOS 16.2 it was:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Parent item identifier does not exist in section snapshot: [item-id]’

Andrew Bennet
  • 2,600
  • 1
  • 21
  • 55