'Diffable Data Source with a custom collection view layout?

Here I have created a sample app that uses diffable data source for a collection view with a custom collection view layout. The specific layout I am using is from this tutorial.

Here is the relevant part of the code if you don't want to clone the repo and try it for yourself.

import UIKit
let cellIdentifier = "testRecordCell"

struct Record:Hashable {
    let identifier = UUID()
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
    static func == (lhs: Record, rhs: Record) -> Bool {
        return lhs.identifier == rhs.identifier
    }
    var timeStamp: Date
    init(daysBack: Int){
        self.timeStamp = Calendar.current.date(byAdding: .day, value: -1*daysBack, to: Date())!
    }
}
class Cell:UICollectionViewCell {
}

class Section: Hashable {
  var id = UUID()
  // 2
  var records:[Record]
  
  init(records:[Record]) {
    self.records = records
  }
  
  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }
  
  static func == (lhs: Section, rhs: Section) -> Bool {
    lhs.id == rhs.id
  }
}

extension Section {
  static var allSections: [Section] = [
    Section(records: [
      Record(daysBack: 5),Record(daysBack: 6)
    ]),
    Section(records: [
      Record(daysBack: 3)
    ])
    ]
}

class ViewController: UICollectionViewController {

    private lazy var dataSource = makeDataSource()
    private var sections = Section.allSections

    fileprivate typealias DataSource = UICollectionViewDiffableDataSource<Section,Record>
    fileprivate typealias DataSourceSnapshot = NSDiffableDataSourceSnapshot<Section,Record>
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView?.register(Cell.self, forCellWithReuseIdentifier: cellIdentifier)
        applySnapshot()
        if let layout = collectionView.collectionViewLayout as? PinterestLayout {
            layout.delegate = self
        }
    }
}
extension ViewController {
    
    fileprivate func makeDataSource() -> DataSource {
        let dataSource = DataSource(
            collectionView: self.collectionView,
            cellProvider: { (collectionView, indexPath, testRecord) ->
              UICollectionViewCell? in
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
                cell.backgroundColor = .black
                return cell
            })
        return dataSource
    }
    func applySnapshot(animatingDifferences: Bool = true) {
        // 2
        var snapshot = DataSourceSnapshot()
        snapshot.appendSections(sections)
        sections.forEach { section in
          snapshot.appendItems(section.records, toSection: section)
        }
        //This part errors out: "request for number of items in section 0 when there are only 0 sections in the collection view"
        dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
    }
}
extension ViewController: PinterestLayoutDelegate {
    func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat {
        return CGFloat(10)
    }
}

Somehow, the layout is not registering that the collection view does have items and sections in it. When you run it normally, it errors out when you are trying to apply the snapshot: "request for number of items in section 0 when there are only 0 sections in the collection view"

Then, in the prepare() function of the Pinterest layout, when I set a breakpoint and inspect collectionView.numberOfSections() it returns 0. So somehow the snapshot is not communicating with the collection view. Notice that I never use the collectionView's delegate method numberOfSections because I am using the diffable data source...

My impression is that diffable data source is usually used with compositional layout though I have not seen anywhere that this is a requirement.

So is there any way to do this?



Solution 1:[1]

The problem is this line:

func applySnapshot(animatingDifferences: Bool = true) {

Change true to false and you won't crash any more.

Solution 2:[2]

Hi it's too late for answer but i tried same way and same problem

My case problem is approach numberOfItems in collectionView

82 lines in PinterestLayout below "for item in 0..<collectionView.numberOfItems(inSection: 0) {"

so.. i inject actual numberOfItems in snapshot from view to custom layout

myLayout.updateNumberOfItems(currentSnapshot.numberOfItems)
dataSource.apply(currentSnapshot)

and just use the numberOfItems instead of collectionView.numberOfItems.

and it's work for me

Please let me know if i'm wrong thx ")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 matt
Solution 2 San Gyeol Kang