"Attempted to dequeue a cell for a different registration or reuse identifier than the existing cell when reconfiguring an item, which is not allowed"

Searching for my very title (including the quotes) generated no results on Google.

If you run this UIKit app and tap the right bar button item:

class ViewController: UIViewController {
    var boolean = false {
        didSet {
            var snapshot = self.snapshot
            snapshot.reconfigureItems(snapshot.itemIdentifiers) // if you comment this out the app doesn't crash
            dataSource.apply(snapshot)
        }
    }
    var snapshot: NSDiffableDataSourceSnapshot<String, String> {
        var snapshot = NSDiffableDataSourceSnapshot<String, String>()
        snapshot.appendSections(["main"])
        snapshot.appendItems(boolean ? ["one"] : ["one", "two"])
        return snapshot
    }
    
    var collectionView: UICollectionView!
    
    var dataSource: UICollectionViewDiffableDataSource<String, String>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureHierarchy()
        configureDataSource()
    }
    
    func configureHierarchy() {
        collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
        view.addSubview(collectionView)
        collectionView.frame = view.bounds
        
        navigationItem.rightBarButtonItem = .init(
            title: "Toggle boolean",
            style: .plain,
            target: self,
            action: #selector(toggleBoolean)
        )
    }
    
    @objc func toggleBoolean() {
        boolean.toggle()
    }
    
    func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { section, layoutEnvironment in
            let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
        }
    }
    
    func configureDataSource() {
        let cellRegistration1 = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
            
        }
        let cellRegistration2 = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
            
        }
        
        dataSource = .init(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
            if indexPath.row == 0 && boolean {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration1, for: indexPath, item: itemIdentifier)
            } else {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration2, for: indexPath, item: itemIdentifier)
            }
        }
        
        dataSource.apply(self.snapshot, animatingDifferences: false)
    }
}

it crashes with that error message.

The app uses a collection view with list layout and a diffable data source.

It has one section, which should show one row if boolean is true, two if it's false.

When boolean changes, the collection view should also reconfigure its items (in my real app, that's needed to update the shown information).

If you comment out snapshot.reconfigureItems(snapshot.itemIdentifiers), the app no longer crashes.

What's the correct way of reconfiguring the items of a diffable data source then?

iOS 17.5, iPhone 15 Pro simulator, Xcode 15.4, macOS 17.5, MacBook Air M1 8GB.

Answered by Frameworks Engineer in 789874022

The problem here is that this code in your cell provider switches between two different cell registrations depending on the value of boolean:

            if indexPath.row == 0 && boolean {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration1, for: indexPath, item: itemIdentifier)
            } else {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration2, for: indexPath, item: itemIdentifier)
            }

but the reconfigure API contract requires you to dequeue a cell using the same cell registration, in order to obtain the existing cell.

Using a different cell registration would require a new cell instance to be dequeued which would need to replace the existing cell — and when you are replacing the cell, that is not a reconfigure operation, that is a reload operation.

So there are two ways to fix this:

  1. If you don't really need to change the entire cell, then when you reconfigure the item, you need to use the same registration in your cell provider. Of course, you can have conditional logic inside that registration which configures the cell differently based on the new state.
  2. If you really need a new cell (e.g. because the type of cell is totally different), then you cannot use reconfigure, and you should instead reload the item.
Accepted Answer

The problem here is that this code in your cell provider switches between two different cell registrations depending on the value of boolean:

            if indexPath.row == 0 && boolean {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration1, for: indexPath, item: itemIdentifier)
            } else {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration2, for: indexPath, item: itemIdentifier)
            }

but the reconfigure API contract requires you to dequeue a cell using the same cell registration, in order to obtain the existing cell.

Using a different cell registration would require a new cell instance to be dequeued which would need to replace the existing cell — and when you are replacing the cell, that is not a reconfigure operation, that is a reload operation.

So there are two ways to fix this:

  1. If you don't really need to change the entire cell, then when you reconfigure the item, you need to use the same registration in your cell provider. Of course, you can have conditional logic inside that registration which configures the cell differently based on the new state.
  2. If you really need a new cell (e.g. because the type of cell is totally different), then you cannot use reconfigure, and you should instead reload the item.
"Attempted to dequeue a cell for a different registration or reuse identifier than the existing cell when reconfiguring an item, which is not allowed"
 
 
Q