Search code examples
swiftuicollectionviewuicollectionviewcellreuseidentifieruicollectionviewdiffabledatasource

Do I need a UICollectionView.CellRegistration for every individual (content)configuration type?


I am a bit struggling with the concepts of UICollectionView.CellRegistration and dequeueConfiguredReusableCell and reusing them. It seems I cannot find the answer on this site either. Sorry if this is somehow a duplicate of a question I didn't find.

Suppose I have a collection view with random cells of 2 (content)configurations: config A and config B. What should be the code to make sure to reuse A cells for 'new' A cells and B for 'new' B cells?

What I currently have is:

enum Section {
    case main
}

enum Foo {
    case configA
    case configB
}

let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Foo> { cell, indexPath, item in
    switch item {
    case .configA:
        cell.contentConfiguration = SomeConfigA()
    case .configB:
        cell.contentConfiguration = SomeConfigB()
    }
}
let dataSource = UICollectionViewDiffableDataSource<Section, Foo>(collectionView: view.collectionView) { collectionView, indexPath, item in
    collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}

But when scrolling I get this warning:

Warning: You are setting a new content configuration to a cell that has an existing content configuration, but the existing content view does not support the new configuration. This means the existing content view must be replaced with a new content view created from the new configuration, instead of updating the existing content view directly, which is expensive. Use separate registrations or reuse identifiers for different types of cells to avoid this. Make a symbolic breakpoint at UIContentConfigurationAlertForReplacedContentView to catch this in the debugger.
Cell: <UICollectionViewListCell: 0x126e4f6a0; frame = (0 826; 375 97.5); layer = <CALayer: 0x6000007d8720>>;
Existing content configuration: SomeConfigA(...);
New content configuration: SomeConfigB(...)

And indeed, if I print out the reuseIdentifier of the cells it is the same for both configurations.

So the warning says: use separate registrations or reuse identifiers. Let's see.

Now I can solve this by using separate registrations:

let cellRegistrationConfigA = UICollectionView.CellRegistration<UICollectionViewListCell, Foo> { cell, indexPath, item in
    cell.contentConfiguration = SomeConfigA()
}
let cellRegistrationConfigB = UICollectionView.CellRegistration<UICollectionViewListCell, Foo> { cell, indexPath, item in
    cell.contentConfiguration = SomeConfigB()
}
let dataSource = UICollectionViewDiffableDataSource<Section, Foo>(collectionView: view.collectionView) { collectionView, indexPath, item in
    switch item {
    case .configA:
        return collectionView.dequeueConfiguredReusableCell(using: cellRegistrationConfigA, for: indexPath, item: item)
    case .ConfigB:
        return collectionView.dequeueConfiguredReusableCell(using: cellRegistrationConfigB, for: indexPath, item: item)
    }
}

Then the warning is gone. So, is this the way to go? You always need to have a separate registration for each configuration? It doesn't seem concise and looks like boilerplate to me. Why can't I have a single registration that handles all possible configurations in a concise way? I usually work with several configurations so this will quickly become messy.

Also, the Apple documentation states that you should not create the registration inside the UICollectionViewDiffableDataSource creation so you need to have a series (array?) of registrations ready beforehand like above.

How can I otherwise keep the original code and change (somehow) the reuse identifier in order to discriminate between the 2 configurations?


Solution

  • If you have two cell types, then you would have two reuse identifiers, and if you have two reuse identifiers and you want to use cell registrations, then you would have two cell registrations.

    Personally, I do not see what your objection is to having two of everything. Do keep in mind, though, that a lot of your objection seems to have to do with the doubling of the cell registration; since it is perfectly possible to do all this without using UICollectionView.CellRegistration at all, perhaps that might be a more pleasant approach for you.

    On the other hand, if you understand what a content configuration is, and how to write one, then an even neater solution might be to have one cell type, one reuse identifier, one cell registration, and one content configuration — a content configuration that, itself, has two possible modes. So instead of setting the cell's content configuration to A or B, you would have just one content configuration which you always assign, and then you would set its mode to A or B. The runtime won't object to that, any more than it would object to setting the configuration's title to one string in one cell and another string in another cell.