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.
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:
- 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.
- 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 insteadreload
the item.