1

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
BigBoy1337
  • 4,735
  • 16
  • 70
  • 138

1 Answers1

1

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)
  }
Jay
  • 2,591
  • 1
  • 16
  • 28
  • I tried snapshot.appendSections([Array(data.keys)]) to do it just once and then do the items section by section. However, the same issue occurs. Though I don't understand why it would be bad to do this section by section? Can you only do it once? – BigBoy1337 Oct 29 '20 at 23:37
  • Hmmm, i see. What do you put in configureHierarchy()? – Jay Oct 30 '20 at 00:06
  • Is there a reason you're using DataSourceSnapshot vs NSDiffableDataSourceSnapshot? – Jay Oct 30 '20 at 00:10
  • do you see my fileprivate typealias for DataSourceSnapshot in the top? I learned that from this tutorial - https://www.raywenderlich.com/8241072-ios-tutorial-collection-view-and-diffable-data-source#toc-anchor-009. I could try not doing that. configureHeirarchy sets my collection view layout to a custom layout. Its probably poorly named – BigBoy1337 Oct 30 '20 at 00:30
  • using `var snapshot = NSDiffableDataSourceSnapshot()` in that applySnapshot function seems to result in the same error. Ill make a project repo so that you can try it. – BigBoy1337 Oct 30 '20 at 00:46
  • So i realized the issue you're facing. When you originally tried to append sections outside of the for loop, you were doing it incorrectly. It should've been snapshot.appendSections(Array(data.keys)). You wrapped the array, with an array. which is incorrect since this wants as flat array, not a nested array. – Jay Oct 30 '20 at 00:52
  • i made the changes to my answer, hopefully it works! when you add ssections in the for loop you overwrite it each time – Jay Oct 30 '20 at 00:58
  • Ok unfortunately when I changed the layout to a normal flow layout instead of my custom layout, it started working, both with this original code, and when using snapshot.appendSections(Array(data.keys)). So I believe that the issue is my custom layout which makes this not a great question. Sorry for the trouble. – BigBoy1337 Oct 30 '20 at 16:54
  • I think this brings up another question though. Why would collectionView.numberOfItems(inSection: 0) error out only when using diffable data source? Perhaps Ill make a different stack overflow question – BigBoy1337 Oct 30 '20 at 17:09
  • Perhaps you should be using the section ID not the index of the section? – Jay Oct 30 '20 at 17:30
  • ok check this out: https://github.com/AlexMarshall12/DiffableDataSourceTest I used a custom collection view layout from this tutorial https://www.raywenderlich.com/4829472-uicollectionview-custom-layout-tutorial-pinterest so I believe Im not making any mistakes there (and it works with a non-diffable data source). However, I get the same issue - collectionView.numberOfSections which causes the question issue when trying to apply a diffable data source snapshot. When using diffable data source you are never explicitly setting those collection view attributes. Maybe they don't get set properly? – BigBoy1337 Oct 30 '20 at 17:39
  • Any update on this? I am also facing this issue. – venky Jun 17 '21 at 11:47