如何修复 "NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0."

How to fix "NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0."

我正在使用 swift 创建一个新的 iOS 应用程序,需要帮助让我的 tableView 删除数据。我正在使用 Core Data 以一对多关系保存数据,我拥有它,所以选择的实体会删除,但在更新 tableView 时会崩溃。

我以前创建过应用程序,但这是我第一次使用一对多数据存储方法。我也用谷歌搜索了解决方案,但 none 有效。这是我的 tableView editingStyle 代码。

if editingStyle == .delete {
    context.delete(items[indexPath.row])

    do {
        try context.save()

        self.tableView.beginUpdates()
        items.remove(at: indexPath.row)
        self.tableView.deleteRows(at: [indexPath], with: .fade)
        self.tableView.endUpdates()
    } catch let error as NSError {
        print("Could not save. \(error.localizedDescription)")
    }
}

我希望该行与 Core Data 中的实体一起删除,但它实际上只是删除实体,然后在尝试更新 tableView 时崩溃。具体来说,我认为它在调用 "deleteRows" 时会崩溃,因为它会给出错误:

Project Aurora[14179:303873] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

更多代码:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let count = fetchedRC.fetchedObjects?.count ?? 0
    return count
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "itemsTable", for: indexPath) as! ItemsTableViewCell

    items.append(fetchedRC.object(at: indexPath))

    if items[indexPath.row].isComplete == true {
        let attributeString = NSMutableAttributedString(string: items[indexPath.row].name!)

        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
        cell.nameL.attributedText = attributeString

    } else {
        cell.nameL.text = items[indexPath.row].name
    }

    return cell
}

您正在使用 NSFetchedResultsController,因此维护一个单独的数组(您的 items)数组不是一个好主意;只需在需要时使用 object(at:) 方法获取项目。

当您将项目附加到 cellForRowAt: 中的 items 数组时,您也遇到了问题 - 对于给定的 indexPathcellForRowAt: 将被多次调用并且无法保证调用 cellForRowAt: 的顺序(例如,当您滚动 table 视图时)。

如果您使用的是 NSFetchedResultsController,您确实应该实现它的委托并使用委托方法来更新您的 table。

摆脱 items 数组并使用类似的东西:

extension ItemsTableViewController: NSFetchedResultsControllerDelegate {
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        self.tableView.beginUpdates()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

        let newIndexPath: IndexPath? = newIndexPath != nil ? IndexPath(row: newIndexPath!.row, section: 0) : nil
        let currentIndexPath: IndexPath? = indexPath != nil ? IndexPath(row: indexPath!.row, section: 0) : nil

        switch type {
        case .insert:
            self.tableView.insertRows(at: [newIndexPath!], with: .automatic)

        case .delete:
            self.tableView.deleteRows(at: [currentIndexPath!], with: .fade)

        case .move:
            self.tableView.moveRow(at: currentIndexPath!, to: newIndexPath!)

        case .update:
            self.tableView.reloadRows(at: [currentIndexPath!], with: .automatic)
        }
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        self.tableView.endUpdates()
    }
}

您的 cellForRowAt 应该是:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "itemsTable", for: indexPath) as! ItemsTableViewCell

    let item = fetchedRC.object(at:indexPath)

    if item.isComplete == true {
        let attributeString = NSMutableAttributedString(string: item.name ?? "")

        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
        cell.nameL.attributedText = attributeString

    } else {
        cell.nameL.text = item.name
    }

    return cell
}

你的删除代码应该是:

if editingStyle == .delete {
    context.delete(fetchedRC.object(at:indexPath))

    do {
        try context.save()
    } catch let error as NSError {
        print("Could not save. \(error.localizedDescription)")
    }
}

记得在创建抓取结果控制器时将 self 分配给 fetchedRC.delegate