2

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?

BigBoy1337
  • 4,735
  • 16
  • 70
  • 138
  • @matt I copied the snapshot append sections code from this tutorial: https://www.raywenderlich.com/8241072-ios-tutorial-collection-view-and-diffable-data-source. Im not sure how its incorrect. – BigBoy1337 Nov 02 '20 at 22:35
  • Yep, that wasn't it. See my answer below. – matt Nov 02 '20 at 22:49
  • It seems to me you asked the same question at https://stackoverflow.com/questions/64509408/uicollectionviewdiffabledatasource-request-for-number-of-items-in-section-0-whe – matt Nov 03 '20 at 00:17
  • @matt I basically did. I asked that one about a specific error I was getting and then this one about a general question. I think this question is better as it involves a miniumal reproducible example and has an actual answer to why the error is occurring. However I'd rather not delete that one as to reward the answerer for the trouble he went through to find an answer. Im guessing its not great to have both questions open but Im not sure what else to do. – BigBoy1337 Nov 03 '20 at 02:32

2 Answers2

2

The problem is this line:

func applySnapshot(animatingDifferences: Bool = true) {

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

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • thanks! perhaps animatingDifferences only work with compositional layout? – BigBoy1337 Nov 02 '20 at 23:03
  • There certainly could be something wrong with your custom layout where it asks questions in the wrong order, if you see what I mean. You can easily test that hypothesis by substituting a different layout experimentally. (I notice that the collection view itself looks terrible even after we stop crashing, which suggests that the layout _is_ flawed.) – matt Nov 03 '20 at 00:20
  • A flow layout does not seem to have this problem. I picked that Pinterest layout because I figured since its in the tutorial it must be an example of a "correct" layout. However maybe its not implementing certain methods that are required for animation that flow layout or compositional layout do implement. I wonder if those layouts have source code online somewhere? – BigBoy1337 Nov 03 '20 at 02:34
  • 1
    “ A flow layout does not seem to have this problem.” Yes, I noticed that too. So I think the layout is flawed. – matt Nov 03 '20 at 03:37
  • Thanks, it works. I've got the same error. And I can't figure out where can be a problem with the custom layout, too. And why I need to turn off animations. Fixing a problem like this looks like a hack. I'm doing something conceptually wrong. – surfrider Feb 16 '21 at 12:24
1

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 ")