DiffableDataSource with multiple cell types
Asked Answered
S

3

14

I'm looking at DiffableDataSource available in iOS13 (or backported here: https://github.com/ra1028/DiffableDataSources) and cannot figure out how one would support multiple cell types in your collection or tableview.

Apple's sample code1 has:

var dataSource: UICollectionViewDiffableDataSource<Section, OutlineItem>! = nil

which seems to force a data source to be a single cell type. If I create a separate data source for another cell type - then there is no guarantee that both data sources don't have apply called on them at the same time - which would lead to the dreaded NSInternalInconsistencyException - which is familiar to anyone who has attempted to animate cell insertion/deletion manually with performBatchUpdates.

Am I missing something obvious?

Splinter answered 14/8, 2019 at 15:9 Comment(1)
did you find a better way to accomplish this one? im still struggling with the same thing. Thanks!Provost
Z
23

I wrapped my different data in an enum with associated values. In my case, my data source was of type UICollectionViewDiffableDataSource<Section, Item>, where Item was

enum Item: Hashable {
  case firstSection(DataModel1)
  case secondSection(DataModel2)
}

then in your closure passed into the data source's initialization, you get an Item, and you can test and unwrap the data, as needed.

(I'd add that you should ensure that your backing associated values are Hashable, or else you'll need to implement that. That is what the diff'ing algorithm uses to identify each cell, and resolve movements, etc)

Zellner answered 15/1, 2020 at 21:8 Comment(2)
More type safe and specific than AnyHashable: 👍Subclavius
Can you please update the answer with the usage of this enum?Ilailaire
S
9

You definitely need to have a single data source.

The key is to use a more generic type. Swift's AnyHashable works well here. And you just need to cast the instance of AnyHashable to a more specific class.

lazy var dataSource = CollectionViewDiffableDataSource<Section, AnyHashable> (collectionView: collectionView) { collectionView, indexPath, item in

        if let article = item as? Article, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Section.articles.cellIdentifier, for: indexPath) as? ArticleCell {
            cell.article = article
            return cell
        }

        if let image = item as? ArticleImage, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Section.trends.cellIdentifier, for: indexPath) as? ImageCell {
            cell.image = image
            return cell
        }

        fatalError()
    }

And the Section enum looks like this:

    enum Section: Int, CaseIterable {
        case articles
        case articleImages

        var cellIdentifier: String {
            switch self {
            case .articles:
                return "articleCell"
            case .articleImages:
                return "imagesCell"
            }
        }
    }

Splinter answered 14/8, 2019 at 20:28 Comment(0)
B
0

One way of achieving this could be taking advantage your Section enum to identify the section with indexPath.section. It will be something like this:

lazy var dataSource = UICollectionViewDiffableDataSource<Section, Item> (collectionView: collectionView) { collectionView, indexPath, item in

        let section = Section(rawValue: indexPath.section)
        switch section {
        case .firstSection:
             let cell = ... Your dequeue code here for first section ...
             return cell
        case .secondSection:
             let cell = ... Your dequeue code here for second section ...
             return cell
        default:
             fatalError() // Here is handling the unmapped case that should not happen
        }
    }
Bebop answered 27/8, 2022 at 4:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.