I am trying to make a timeline where each section is a day and each day has many items (records). Here is my section (day) class:

class YearMonthDay: Comparable, Hashable {
    let year: Int
    let month: Int
    let day: Int
    
    init(year: Int, month: Int, day: Int) {
        self.year = year
        self.month = month
        self.day = day
    }

    init(date: Date) {
        let comps = Calendar.current.dateComponents([.year, .month, .day], from: date)
        self.year = comps.year!
        self.month = comps.month!
        self.day = comps.day!
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(year)
        hasher.combine(month)
        hasher.combine(day)
    }
    
    var date: Date {
        var dateComponents = DateComponents()
        dateComponents.year = year
        dateComponents.month = month
        dateComponents.day = day
        return Calendar.current.date(from: dateComponents)!
    }

    static func == (lhs: YearMonthDay, rhs: YearMonthDay) -> Bool {
        return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
    }

    static func < (lhs: YearMonthDay, rhs: YearMonthDay) -> Bool {
        if lhs.year != rhs.year {
            return lhs.year < rhs.year
        } else {
            if lhs.month != rhs.month {
                return lhs.month < rhs.month
            } else {
                return lhs.day < rhs.day
            }
        }
    }
}

As you can see I am adding year month and day attributes to my sections and using them to make each one "washable" so hopefully there will be only 1 section for each day at most.

Here is my collectionViewController...

class TimelineViewController: UICollectionViewController {

    private lazy var dataSource = makeDataSource()
    
    fileprivate typealias DataSource = UICollectionViewDiffableDataSource<YearMonthDay,TestRecord>
    fileprivate typealias DataSourceSnapshot = NSDiffableDataSourceSnapshot<YearMonthDay,TestRecord>
    
    public var data: [YearMonthDay:[TestRecord]] = [:]
    var delegate: TimelineViewControllerDelegate?
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let data = delegate?.dataForTimelineView() else { return }
        self.data = data
        collectionView.showsHorizontalScrollIndicator = true
        configureHierarchy()
        configureDataSource()
        applySnapshot()
        
    }
}
extension TimelineViewController {
    fileprivate func makeDataSource() -> DataSource {
        let dataSource = DataSource(
            collectionView: collectionView,
            cellProvider: { (collectionView, indexPath, testRecord) ->
              UICollectionViewCell? in
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimelineDayCell.identifier, for: indexPath) as? TimelineDayCell
                cell?.configure(with: testRecord)
                cell?.dayLabel.text = String(indexPath.section)+","+String(indexPath.row)
                return cell
            })
        return dataSource
    }
    func configureDataSource() {
        self.collectionView!.register(TimelineDayCell.nib, forCellWithReuseIdentifier: TimelineDayCell.identifier)
    }
    func applySnapshot(animatingDifferences: Bool = true) {
      // 2
      var snapshot = DataSourceSnapshot()
      for (ymd,records) in data {
          snapshot.appendSections([ymd])
          snapshot.appendItems(records,toSection: ymd)
      }
      // This is where the error occurs. 
      dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
    }
}

But I get this crash error:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for number of items in section 0 when there are only 0 sections in the collection view'

If I look at data, I can see that all of it is being populated correctly and when I print out snapshot.sectionIdentifiers it gives me all of those YearMonthDay objects. But somehow the dataSource is seeing 0 sections?

I think for a normal data source, I would just implement the delegate method - numberOfSections but since this is a diffable data source Im not sure what to do...

Edit:

data from the delegate is a dictionary of this type: [YearMonthDay:[TestRecord]]. Here is what it looks like when printed out

▿ 7 elements
  ▿ 0 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e880f0>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : 5D05C73D-8863-47E3-B1E5-3331791A3FB8
        - type : SweatNetOffline.RecordType
        - progression : 1
        ▿ timeStamp : 2020-10-16 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 624517019.639298
  ▿ 1 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e89ec0>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : C40F5884-AABF-43C8-9921-95DD1423AC4D
        - type : SweatNetOffline.RecordType
        - progression : 1
        ▿ timeStamp : 2020-10-22 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 625035419.639274
  ▿ 2 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e88cf0>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : 5EE70ABF-4C4D-4FB5-A850-3D0081D91D0D
        - type : SweatNetOffline.RecordType
        - progression : 2
        ▿ timeStamp : 2020-10-20 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 624862619.639284
  ▿ 3 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e8a0a0>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : 59152DCA-63EC-4EBF-ACDB-B45FA5E85EED
        - type : SweatNetOffline.RecordType
        - progression : 2
        ▿ timeStamp : 2020-10-18 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 624689819.639291
  ▿ 4 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e89890>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : 0E12D3D6-0650-41A6-AC12-EB75EFA0A151
        - type : SweatNetOffline.RecordType
        - progression : 0
        ▿ timeStamp : 2020-10-26 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 625381019.638627
  ▿ 5 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e89e60>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : 99A929C4-B94B-4F8F-8C6D-2C356D778D95
        - type : SweatNetOffline.RecordType
        - progression : 2
        ▿ timeStamp : 2020-10-24 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 625208219.639251
  ▿ 6 : 2 elements
    ▿ key : <YearMonthDay: 0x600000e89f20>
    ▿ value : 1 element
      ▿ 0 : TestRecord
        - identifier : EF6EB971-504B-4587-8038-FB8C3FD7ACDC
        - type : SweatNetOffline.RecordType
        - progression : 1
        ▿ timeStamp : 2020-10-14 04:56:59 +0000
          - timeIntervalSinceReferenceDate : 624344219.639307
1

There are 1 answers

12
Jay On

I'm not super familiar with this new way of constructing a collectionView, but I believe the problem could be due to this line of code

snapshot.appendSections([ymd])

Why are you passing in an array here, for each iteration in the For loop? This should be done once, should it not?

for (ymd,records) in data {
      snapshot.appendSections([ymd])
      snapshot.appendItems(records,toSection: ymd)
}

to

snapshot.appendSections(Array(data.keys))
for (ymd,records) in data {
      snapshot.appendItems(records,toSection: ymd)
  }