UICollectionView raises an exception when dequeuing a cell using a registration on iOS 15

This thread has been locked by a moderator.

I have a UICollectionView in my app which uses UICollectionView.CellRegistration (Swift) / UICollectionViewCellRegistration (Objective-C) to register & dequeue cells.

When I build my app using Xcode 13 and run it on iOS 15, I see the following exception from UICollectionView:

Assertion failure in -[UICollectionView dequeueConfiguredReusableCellWithRegistration:forIndexPath:item:], UICollectionView.m:7160

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempted to dequeue a cell using a registration that was created inside -collectionView:cellForItemAtIndexPath: or inside a UICollectionViewDiffableDataSource cell provider. Creating a new registration each time a cell is requested will prevent reuse and cause created cells to remain inaccessible in memory for the lifetime of the collection view. Registrations should be created up front and reused. Registration: <UICollectionViewCellRegistration: 0x186ef8b10>'

Why is my app crashing with this error?

Up vote post of Frameworks Engineer
5.6k views

Accepted Reply

If you're seeing this exception, it means that you created your cell registration inside the callback from UICollectionView when it asked you to return a cell — either inside the collectionView(_:cellForItemAt:) data source method, or inside your UICollectionViewDiffableDataSource cell provider closure.

This exception was added in iOS 15 to warn you about cases where you may have been creating a new cell registration each time a cell was requested. This was an easy mistake to make because everything would seem like it worked on the surface, but behind the scenes your cells were never being reused, and they would continue accumulating in memory in the collection view's reuse pool, resulting in serious performance issues and crashes for your users.

The correct way to use cell registrations is to create all of them up front — before UICollectionView requests any cells — and then capture the registrations in your UICollectionViewDiffableDataSource cell provider closure (or if you aren't using diffable data source, store them for use inside your collectionView(_:cellForItemAt:) implementation.). Then all you need to do is to choose the right registration to use for the requested item.

Here's an example of how that should look:

func setupDiffableDataSource() {

    // Create registrations up front and store them in local variables.
    
    let cellRegistration1 = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, item in
        // ...
    }
        
    let cellRegistration2 = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, item in
        // ...
    }
    
    // Capture the registrations inside the cell provider, and choose which registration to use for each item.
    
    dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
        if /* your logic to choose which registration to use here */ {
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration1, for: indexPath, item: item)
        } else {
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration2, for: indexPath, item: item)
        }
    }
}

Creating registrations inside the cell provider isn't supported on iOS 15, even if you were being extra careful to only create each type of registration exactly once (e.g. by storing them in lazy properties). But the fix is easy: just make sure that your registrations are created before any cells are requested.

Replies

If you're seeing this exception, it means that you created your cell registration inside the callback from UICollectionView when it asked you to return a cell — either inside the collectionView(_:cellForItemAt:) data source method, or inside your UICollectionViewDiffableDataSource cell provider closure.

This exception was added in iOS 15 to warn you about cases where you may have been creating a new cell registration each time a cell was requested. This was an easy mistake to make because everything would seem like it worked on the surface, but behind the scenes your cells were never being reused, and they would continue accumulating in memory in the collection view's reuse pool, resulting in serious performance issues and crashes for your users.

The correct way to use cell registrations is to create all of them up front — before UICollectionView requests any cells — and then capture the registrations in your UICollectionViewDiffableDataSource cell provider closure (or if you aren't using diffable data source, store them for use inside your collectionView(_:cellForItemAt:) implementation.). Then all you need to do is to choose the right registration to use for the requested item.

Here's an example of how that should look:

func setupDiffableDataSource() {

    // Create registrations up front and store them in local variables.
    
    let cellRegistration1 = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, item in
        // ...
    }
        
    let cellRegistration2 = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, item in
        // ...
    }
    
    // Capture the registrations inside the cell provider, and choose which registration to use for each item.
    
    dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
        if /* your logic to choose which registration to use here */ {
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration1, for: indexPath, item: item)
        } else {
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration2, for: indexPath, item: item)
        }
    }
}

Creating registrations inside the cell provider isn't supported on iOS 15, even if you were being extra careful to only create each type of registration exactly once (e.g. by storing them in lazy properties). But the fix is easy: just make sure that your registrations are created before any cells are requested.