UICollectionViewCompositionalLayout can't use the same sections as UICollectionViewDiffableDataSource?

872 views Asked by At

I'm building a view controller with a collection view, using the (kinda) new diffable datasource and composable collectionview layout. I made my own Section and Row enums, can react to changes in my model (an array of photos), which triggers the applySnapshot and everything works.

But.. in my createLayout function I only have a section index to work with?? This seems very weird to me, why don't these two technologies work better together? One works with SectionIdentifierType and ItemIdentifierType (great!) and the other is still index based? Ugh.

What I want to be able to do is something like this: all cells within the Section.header section need to be full screen width, with an automatic height. Every cell in the Section.photos section will get a fixed size. And there can be other sections with different cell sizes too, and some sections can be optional. So an index-based system is really crap to work with, can't hardcode that section 0 is the header and section 1 is photos: the order can change, things might be optional, etc.

What is a better wat to deal with this? I can of course store an array [Section] on the view controller, use the section index to get the Section case that way, but is there no built-in way to get the section identifier for the index? It seems like such a logical thing since these 2 technologies were released at the same time.

class PhotosViewController: UIViewController {
  enum Section: Equatable, Hashable {
    case header
    case photos
  }

  enum Row: Equatable, Hashable {
    case header
    case photo(Photo)
  }

  private lazy var dataSource = makeDataSource()
  var photos: AnyPublisher<[Photo], Never>!

  override func viewDidLoad() {
    super.viewDidLoad()

    collectionView.collectionViewLayout = createLayout()

    photos
      .sink(receiveValue: applySnapshot)
      .store(in: &subscriptions)
  }

  private func makeDataSource() -> UICollectionViewDiffableDataSource<Section, Row> {
    return UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
      switch item {
        case .header:
          let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: ProfileHeaderCollectionViewCell.self)
          return cell

        case .photo(let photo):
          let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: PhotoCollectionViewCell.self)
          cell.configure(photo: photo)
          return cell
      }
    }
  }

  private func applySnapshot(photos: [Photo]) {
    var snapshot = NSDiffableDataSourceSnapshot<Section, Row>()

    let headerSection = Section.header
    snapshot.appendSections([headerSection])
    snapshot.appendItems([.header], toSection: headerSection)

    let items = photos.map { Row.photo($0) }
    let photosSection = Section.photos
    snapshot.appendSections([photosSection])
    snapshot.appendItems(items, toSection: photosSection)

    dataSource.apply(snapshot, animatingDifferences: false)
  }

  private func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, _ in
      // I have to work with a sectionIndex instead of a Section case?
    }

    return layout
  }
}
1

There are 1 answers

0
Kevin Renskers On BEST ANSWER

Instead of keeping track of the section identifiers in a separate array, luckily you can just use dataSource.snapshot().sectionIdentifiers[sectionIndex].