UICollectionViewDiffableDataSource crash: Invalid parameter not satisfying: itemCount

953 views Asked by At

I am using a UICollectionView with UICollectionViewDiffableDataSource and fetching data both asynchronously and synchronously to populate the view. The app crashes with this message: "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: itemCount'."

I believe it has something to do with the combination of sync and async data fetching, as when I implement one or the other the app runs as expected.

A simple version of the code follows:

struct Model: Hashable {
  let id: UUID
}

class ViewController: UIViewController {
  
  private var collectionView: UICollectionView!
  private var dataSource: DataSource?
  private var asyncItems: [Model] = []
  private var syncItems: [Model] = []
  
  typealias DataSource = UICollectionViewDiffableDataSource<Section, Model>
  typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Model>
  
  enum Section {
    case async
    case sync
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    setUpCollectionView()
    setUpDataSource()
    
    fetchData()
  }
  
  func setUpCollectionView() {
    let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: setUpCollectionViewLayout())
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(collectionView)
    collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "reuseIdentifier")
    
    self.collectionView = collectionView
  }
  
  func setUpCollectionViewLayout() -> UICollectionViewLayout {
    return UICollectionViewCompositionalLayout { (sectionIndex: Int,
      layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
      let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(1.0))
      let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
      
      let item = NSCollectionLayoutItem(layoutSize: itemSize)
      
      let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
      
      return NSCollectionLayoutSection(group: group)
    }
  }
  
  func setUpDataSource() {
    self.dataSource =  DataSource(collectionView: collectionView) { (collectionView, indexPath, model) -> UICollectionViewCell? in
      let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "reuseIdentifier", for: indexPath)
      return cell
    }
  }
  
  func fetchData() {
    let dispatchGroup = DispatchGroup()
    
    dispatchGroup.enter()
    DispatchQueue.global(qos: .background).async { [weak self] in
      defer {
        dispatchGroup.leave()
      }
      self?.asyncItems = [Model(id: UUID())]
    }
    
    syncItems = [Model(id: UUID())]
    
    dispatchGroup.notify(queue: .main) { [weak self] in
      self?.applySnapshot()
    }
  }
  
  func applySnapshot() {
    var snapshot = Snapshot()
    snapshot.appendSections([Section.async])
    snapshot.appendItems(asyncItems)
    
    snapshot.appendSections([Section.sync])
    snapshot.appendItems(syncItems)
    
    dataSource?.apply(snapshot)
  }
}

Debugger output with stack trace:

2020-09-04 16:42:26.859624-0500 DiffableDataSource[81736:1400590] *** Assertion failure in -[_UICollectionLayoutItemSolver _frameForAbsoluteIndex:additionalFrameOffset:interSolutionSpacing:repeatAxis:], /Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3920.31.102/_UICollectionLayoutItemSolver.m:1241
2020-09-04 16:42:26.867684-0500 DiffableDataSource[81736:1400590] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: itemCount'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff23e3de6e __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff512a19b2 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff23e3dbe8 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x00007fff258e6bd2 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   UIKitCore                           0x00007fff48abe254 -[_UICollectionLayoutItemSolver _frameForAbsoluteIndex:additionalFrameOffset:interSolutionSpacing:repeatAxis:] + 429
    5   UIKitCore                           0x00007fff48ac9fe6 -[_UICollectionLayoutSectionFixedSolver frameForIndex:] + 234
    6   UIKitCore                           0x00007fff48a986d0 -[_UICollectionCompositionalLayoutSolver layoutAttributesForItemAtIndexPath:] + 355
    7   UIKitCore                           0x00007fff48b730d1 -[UICollectionViewLayout initialLayoutAttributesForAppearingItemAtIndexPath:] + 267
    8   UIKitCore                           0x00007fff48b3d59e __51-[UICollectionView _viewAnimationsForCurrentUpdate]_block_invoke.1814 + 105
    9   UIKitCore                           0x00007fff48b3a360 -[UICollectionView _viewAnimationsForCurrentUpdate] + 3213
    10  UIKitCore                           0x00007fff48b41617 __71-[UICollectionView _updateWithItems:tentativelyForReordering:animator:]_block_invoke.1887 + 118
    11  UIKitCore                           0x00007fff4986c650 +[UIView(Animation) performWithoutAnimation:] + 84
    12  UIKitCore                           0x00007fff48b40484 -[UICollectionView _updateWithItems:tentativelyForReordering:animator:] + 3975
    13  UIKitCore                           0x00007fff48b39005 -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] + 16761
    14  UIKitCore                           0x00007fff48b42a83 -[UICollectionView _endUpdatesWithInvalidationContext:tentativelyForReordering:animator:] + 71
    15  UIKitCore                           0x00007fff48b42de2 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:animator:] + 462
    16  UIKitCore                           0x00007fff48b42bf1 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:] + 90
    17  UIKitCore                           0x00007fff48b42b74 -[UICollectionView _performBatchUpdates:completion:invalidationContext:] + 74
    18  UIKitCore                           0x00007fff48b42ac9 -[UICollectionView performBatchUpdates:completion:] + 53
    19  UIKitCore                           0x00007fff48b523ce -[UICollectionView _performDiffableUpdate:] + 44
    20  UIKitCore                           0x00007fff48af09ea -[_UIDiffableDataSourceViewUpdater _performUpdateWithCollectionViewUpdateItems:dataSourceSnapshotter:updateHandler:completion:] + 467
    21  UIKitCore                           0x00007fff48ae9ab6 -[__UIDiffableDataSource _commitNewDataSource:withViewUpdates:completion:] + 246
    22  UIKitCore                           0x00007fff48ae449e __66-[__UIDiffableDataSource applyDifferencesFromSnapshot:completion:]_block_invoke.154 + 190
    23  UIKitCore                           0x00007fff48ae472d __66-[__UIDiffableDataSource applyDifferencesFromSnapshot:completion:]_block_invoke.180 + 107
    24  libdispatch.dylib                   0x00000001071b0e8e _dispatch_client_callout + 8
    25  libdispatch.dylib                   0x00000001071bfae2 _dispatch_lane_barrier_sync_invoke_and_complete + 132
    26  UIKitCore                           0x00007fff48ae3fa7 -[__UIDiffableDataSource applyDifferencesFromSnapshot:completion:] + 952
    27  UIKitCore                           0x00007fff48ae4fb9 -[__UIDiffableDataSource applyDifferencesFromSnapshot:animatingDifferences:completion:] + 71
    28  libswiftUIKit.dylib                 0x00007fff51ed4af4 $s5UIKit34UICollectionViewDiffableDataSourceC5apply_20animatingDifferences10completionyAA010NSDiffableeF8SnapshotVyxq_G_SbyycSgtFTm + 212
    29  DiffableDataSource                  0x0000000106f2f087 $s18DiffableDataSource14ViewControllerC13applySnapshotyyF + 823
    30  DiffableDataSource                  0x0000000106f2ec37 $s18DiffableDataSource14ViewControllerC05fetchB0yyFyycfU0_ + 215
    31  DiffableDataSource                  0x0000000106f2ea40 $sIeg_IeyB_TR + 48
    32  libdispatch.dylib                   0x00000001071aff11 _dispatch_call_block_and_release + 12
    33  libdispatch.dylib                   0x00000001071b0e8e _dispatch_client_callout + 8
    34  libdispatch.dylib                   0x00000001071bed97 _dispatch_main_queue_callback_4CF + 1149
    35  CoreFoundation                      0x00007fff23da1869 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    36  CoreFoundation                      0x00007fff23d9c3b9 __CFRunLoopRun + 2041
    37  CoreFoundation                      0x00007fff23d9b8a4 CFRunLoopRunSpecific + 404
    38  GraphicsServices                    0x00007fff38c05bbe GSEventRunModal + 139
    39  UIKitCore                           0x00007fff49372964 UIApplicationMain + 1605
    40  DiffableDataSource                  0x0000000106f30f4b main + 75
    41  libdyld.dylib                       0x00007fff5211c1fd start + 1
    42  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
1

There are 1 answers

0
Rachel On BEST ANSWER

Fix for this crash:

When setting up the collection view layout group, explicitly provide the item count:

let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 1)