-1

I am trying to apply an empty snapshot, it's crashing my app. I have been trying to debug it for 2 days now and can't seem to figure out a way to resolve this issue. Below is the code I am running:

//
//  ItemsListDiffableVC.swift
//  FetchRewardsCodingExercise
//
//  Created by Vandan Patel on 11/26/20.
//

import UIKit

final class ItemsListDiffableVC: UIViewController {
    private var tableView: UITableView!
    private var dataSource: ItemDataSource!
    private var groupedItems = [Dictionary<Int, [Item]>.Element]()
    
    var presenter: ItemsListPresentable!
    
    private let cellReusableID = "itemCell"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureTableView()
        presenter.didLoadView()
    }
    
    private func configureTableView() {
        tableView = UITableView()
        tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        tableView.backgroundColor = .systemGroupedBackground
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReusableID)
        view.addSubview(tableView)
    }
    
    private func configureDataSource() {
        dataSource = ItemDataSource(tableView: self.tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
            let cell = tableView.dequeueReusableCell(withIdentifier: self.cellReusableID, for: indexPath) as! ItemCell
            cell.configureCell(withTitle: item.name ?? "")
            return cell
        })
    }
}

extension ItemsListDiffableVC: ItemsListViewable {
    func display(groupedItems: [Dictionary<Int, [Item]>.Element]) {
        DispatchQueue.main.async {
            self.configureDataSource()
            self.update(with: groupedItems)
        }
    }
    
    func display(error: String) {
    }
}

extension ItemsListDiffableVC {
    private func update(with groupedItems: [Dictionary<Int, [Item]>.Element]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
    }
}

class Section: Hashable {
    
    let sectionName: String
    let identifier = UUID()
    
    init(sectionName: String) {
        self.sectionName = sectionName
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
    
    static func == (lhs: Section, rhs: Section) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

class ItemDataSource: UITableViewDiffableDataSource<Section, Item> {
    var groupedItems = [Dictionary<Int, [Item]>.Element]()
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return groupedItems[section].key.sectionTitle
    }
}

struct Item: Codable, Hashable {
    let id: Int
    let listId: Int
    let name: String?
}

This is the error I am getting:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (0) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

What I don't understand is why there is one section even before the update and how to take care of it.

Thanks.

Vandan Patel
  • 1,012
  • 1
  • 12
  • 23
  • What is an Item? And where is your _data_? And is the purpose of applying an empty snapshot? Please show your real code and and explain your real goal. – matt Nov 29 '20 at 17:42
  • Also, `[Dictionary.Element]` is an odd way to talk. – matt Nov 29 '20 at 18:30
  • I just wanted help figuring out why the app is crashing. I applied an empty snapshot so that we can see off by 1 error easily. Even though I am applying an empty snapshot, the error says it has 1 section even before the update. That's what I am trying to figure out. 1. Why is it 1 section as soon as I initialize tableView? 2. If it is 1, how do I make it 0? Thanks! – Vandan Patel Nov 29 '20 at 18:42
  • Well, you haven't given enough code to test the problem. Please provide a working, compilable, self-contained example that performs the crash. – matt Nov 29 '20 at 18:45
  • Your code still doesn't compile. What's an ItemsListPresentable? What's an ItemCell? What's an ItemsListViewable? Where does the `sectionTitle` property come from? If I can't compile it I can't run it, if I can't run it I can't crash. – matt Nov 29 '20 at 18:51
  • By the way the table view has no size, did you know that? That can mess things up too. – matt Nov 29 '20 at 18:52
  • I know that and that's not my focus as of now. I just want to take care of the crash. – Vandan Patel Nov 29 '20 at 19:55
  • Me too! And as soon as you provide code that I can actually run and crash, I will try to help with that. – matt Nov 29 '20 at 19:58

4 Answers4

1

I had the same issue what i did is turning animatingDifferences to false and the crash was gone

droid
  • 581
  • 7
  • 16
0

I suspect that the problem lies in code you have not shown us (since the code is obviously incomplete; you don't declare Item, and you don't seem to have any data anywhere). However, here are some thoughts about the code you did show:


This makes no sense:

    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    dataSource.apply(snapshot, animatingDifferences: true, completion: nil)

That snapshot is empty. So there is no data to update and no differences to apply. Your function is named func update(with groupedItems: but in fact no matter what groupedItems may be passed to you, you are just throwing them away and using this empty snapshot.


Also, after initially populating the data source, which happens just once, you never ever create a snapshot again. You always get each succeeding snapshot from the data source, modify it, and apply it.


Also, this shows a misconception about what a diffable data source is:

class ItemDataSource: UITableViewDiffableDataSource<Section, Item> {
    var groupedItems = [Dictionary<Int, [Item]>.Element]()
}

You do not use a separate instance variable to hold the data in a diffable data source. The diffable data source itself holds the data, which is handed to it by the snapshot when it is applied.

Basically, you need to decide where the source of truth lies for this data, and populate the table view data source consistently from there. The usual thing is that the diffable data source itself is the source of truth; if you have a good reason to use a "backing store", fine (perhaps your data updates itself asynchronously from the network?), but then don't use two of them.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I am applying an empty snapshot to make it easier to debug. What doesn't make sense is that why would it crash when you apply an empty snapshot. This crash is specific to sections and I have shared all the related code. However, I don't mind sharing more. What I have noticed is tableView has 1 section as soon as it is initialized and that makes it off by 1 and thus the crash. Thanks for taking the time out to respond. – Vandan Patel Nov 29 '20 at 18:33
0

First thing first, UIDiffableDataSource is not made for reference type, so i would suggest please convert your all model classes to structure.

CrackIt
  • 606
  • 1
  • 5
  • 15
  • This is not accurate. As long as a type is hashable, it's fine. – Drew Jun 10 '21 at 15:43
  • @Drew initially i thought the same. But in one of demo project i was having issue and I filed the bug in apple feedback assistant and look at their reply. https://ibb.co/qgpvhCZ so i was in shock so I decided to investigate further and i checked all diffable datasource demos provided by apple and I found none of their example uses reference type. – CrackIt Jun 10 '21 at 18:27
  • .@CrackIt That's interesting because the docs say nothing about that requirement. I've also been able to get diffable data sources working using just classes.That's interesting because the docs say nothing about that requirement. I've also been able to get diffable data sources working using just classes. – Drew Jun 16 '21 at 20:32
  • yeah @Drew. So I am totally confused right now. generally I avoid using reference type as of now. Although there some new changes in recent WWDC 21 you should check out. We might be using this whole diffable datasource thing wrong way! – CrackIt Jun 17 '21 at 06:24
0
tableView.dataSource = dataSource

I don't see that anywhere in the code presented.

Nobadi
  • 121
  • 2
  • 13