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
}
}
Instead of keeping track of the section identifiers in a separate array, luckily you can just use
dataSource.snapshot().sectionIdentifiers[sectionIndex]
.