Browse Questions
- A diffable data source is a data source that you drive with immutable snapshots of Hashable identifiers instead of index-path callbacks, and it computes the inserts, deletes, and moves for you.
- Encapsulation, abstraction, inheritance, and polymorphism.
- layoutSubviews is called by the system whenever a view's bounds change or its subview layout needs to be recalculated.
- @ViewBuilder is a result builder that lets you write multiple views inside a closure and have them composed into a single view.
Answer:
A diffable data source is a data source that you drive with immutable snapshots of Hashable identifiers instead of index-path callbacks, and it computes the inserts, deletes, and moves for you. It eliminates the entire class of "invalid number of rows" crashes caused by the model and the performBatchUpdates calls falling out of sync.
Code Example:
enum Section: Hashable { case pinned, all }
struct Contact: Hashable, Identifiable {
let id: UUID
let name: String
let isPinned: Bool
}
final class ContactsViewController: UIViewController {
private var dataSource: UICollectionViewDiffableDataSource<Section, Contact.ID>!
private var contactsByID: [Contact.ID: Contact] = [:]
private func makeDataSource() {
let cellReg = UICollectionView.CellRegistration<UICollectionViewListCell, Contact.ID> {
[weak self] cell, _, id in
var config = cell.defaultContentConfiguration()
config.text = self?.contactsByID[id]?.name
cell.contentConfiguration = config
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, id in
collectionView.dequeueConfiguredReusableCell(using: cellReg, for: indexPath, item: id)
}
}
private func apply(_ contacts: [Contact], animated: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Contact.ID>()
snapshot.appendSections([.pinned, .all])
snapshot.appendItems(contacts.filter(\.isPinned).map(\.id), toSection: .pinned)
snapshot.appendItems(contacts.map(\.id), toSection: .all)
dataSource.apply(snapshot, animatingDifferences: animated) // diffs + animates for you
}
}
// Compositional layout — list appearance with a header
let layout = UICollectionViewCompositionalLayout { _, environment in
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.headerMode = .supplementary
return .list(using: config, layoutEnvironment: environment)
}
Key Points:
- Section and item types must be
Hashable, and each item's hash must be stable and unique — prefer anIDover the whole model, otherwise editing a field reads as delete + insert apply(_:animatingDifferences:)is safe to call from a background queue as long as you're consistent; on iOS 15+ useawait dataSource.apply(snapshot)- Use
reconfigureItems(iOS 15+) instead ofreloadItemsto update a visible cell in place without recreating it - Pairs naturally with
UICollectionViewCompositionalLayoutandCellRegistration, which remove theregister/reuseIdentifierstring dance