Reputation: 3239
Here is the error:
Thread 1: "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: 0x60000135b300>"
Here is file that is causing the error:
import UIKit
class PetExplorerViewController: UICollectionViewController {
// MARK: - Properties
var adoptions = Set<Pet>()
lazy var dataSource = makeDataSource()
// MARK: - Types
enum Section: Int, CaseIterable, Hashable {
case availablePets
case adoptedPets
}
typealias DataSource = UICollectionViewDiffableDataSource<Section, Item>
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Pet Explorer"
configureLayout()
applyInitialSnapshots()
}
// MARK: - Functions
func configureLayout() {
let configuration = UICollectionLayoutListConfiguration(appearance: .grouped)
collectionView.collectionViewLayout = UICollectionViewCompositionalLayout.list(using: configuration)
}
func applyInitialSnapshots() {
var categorySnapshots = NSDiffableDataSourceSnapshot<Section, Item>()
let categories = Pet.Category.allCases.map { category in
return Item(title: String(describing: category))
}
categorySnapshots.appendSections([.availablePets])
categorySnapshots.appendItems(categories, toSection: .availablePets)
dataSource.apply(categorySnapshots, animatingDifferences: false)
}
func makeDataSource() -> DataSource {
return DataSource(collectionView: collectionView) {collectionView, indexPath, item in
return collectionView.dequeueConfiguredReusableCell(using: self.categoryCellRegistration(), for: indexPath, item: item)
}
}
}
// MARK: - CollectionView Cells
extension PetExplorerViewController {
}
// MARK: - UICollectionViewDelegate
extension PetExplorerViewController {
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
func pushDetailForPet(_ pet: Pet, withAdoptionStatus isAdopted: Bool) {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let petDetailViewController =
storyboard.instantiateViewController(identifier: "PetDetailViewController") { coder in
return PetDetailViewController(coder: coder, pet: pet)
}
petDetailViewController.delegate = self
petDetailViewController.isAdopted = isAdopted
navigationController?.pushViewController(petDetailViewController, animated: true)
}
}
// MARK: - PetDetailViewControllerDelegate
extension PetExplorerViewController: PetDetailViewControllerDelegate {
func petDetailViewController(_ petDetailViewController: PetDetailViewController, didAdoptPet pet: Pet) {
}
func categoryCellRegistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
return .init { cell, _, item in
var configuration = cell.defaultContentConfiguration()
configuration.text = item.title
cell.contentConfiguration = configuration
}
}
}
I think that should be all that's needed, but would be more than willing to add any other relevant needed for troubleshooting.
Upvotes: 5
Views: 3483
Reputation: 1
It can also happen if you store your registration as static let
somewhere
but call it somewhere but not in UICollectionViewDiffableDataSource.CellProvider closure
to fix it you can just add all registrations in capture list
final class DiffableDataSource<Section: SectionProtocol, Item: ItemProtocol>:
UICollectionViewDiffableDataSource<Section, Item> {
/// diffable dataSources owns a view, if you wand different dataSource
/// make a different collectionView
required init(collectionView: UICollectionView) {
let registrations = [
TestingTextCell.registration,
TestingColorCell.registration,
TestingSuplementaryFooterView.registration,
TestingSuplementaryHeaderView.registration,
TestingSuplementaryDividerView.registration
] as [Any]
let cellProvider: CellProvider = { [registrations] collectionView, indexPath, item in
item.dequeueConfiguredReusableCell(in: collectionView, for: indexPath)
}
super.init(collectionView: collectionView, cellProvider: cellProvider)
self.supplementaryViewProvider = suplementaryProvider
}
supplementaryViewProvider
doesn't require capture list if cellProvider
already captured it
Upvotes: 0
Reputation: 10112
This was added in iOS 15
for warning you against API misuse. There's an explanation for it here -
UICollectionView raises an exception when dequeuing a cell using a registration on iOS 15
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.
Here's what you should do -
// Remove following -
/*
func categoryCellRegistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
return .init { cell, _, item in
var configuration = cell.defaultContentConfiguration()
configuration.text = item.title
cell.contentConfiguration = configuration
}
}
*/
// Add following
let categoryCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> { cell, _, item in
var configuration = cell.defaultContentConfiguration()
configuration.text = item.title
cell.contentConfiguration = configuration
}
// Update this line -
// `categoryCellRegistration` is not a function anymore
return collectionView.dequeueConfiguredReusableCell(using: self.categoryCellRegistration, for: indexPath, item: item)
Upvotes: 8
Reputation: 535889
The problem is your categoryCellRegistration
. This should be a constant property (a UICollectionView.CellRegistration instance), not a method that you call every time.
Upvotes: 2