UITableViewDiffableDataSouce: Invalid update: invalid number of sections

1.5k views Asked by At

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.

4

There are 4 answers

4
CrackIt On

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

1
matt On

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.

0
droid On

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

0
Nobadi On
tableView.dataSource = dataSource

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