Crash on cell deletion/insertion in sectioned list #76

Closed
opened 2025-12-29 15:23:45 +01:00 by adam · 6 comments
Owner

Originally created by @avshiyanov on GitHub (Sep 8, 2016).

I have many sections on the list, for example 5, first one have only one element in section, when I do transation.delete(obj) App crashes. The same occurs if section does not exist and I do insert
Crash
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)

Here is deletion logic:
CoreStore.beginAsynchronous { (transaction) -> Void in
transaction.delete(task)
transaction.commit()
}

In data source I have:
// MARK: ListObserver

func listMonitorWillChange(monitor: ListMonitor<ListEntityType>) {
    self.tableView.beginUpdates()
}

func listMonitorDidChange(monitor: ListMonitor<ListEntityType>) {
    self.tableView.endUpdates()
}

func listMonitor(monitor: ListMonitor<ListEntityType>,
                 didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath) {
    print (indexPath)
    print (listMonitor.numberOfSections())
    self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}

func listMonitor(monitor: ListMonitor<ListEntityType>,
                 didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath) {
    print (indexPath)
    print (listMonitor.numberOfSections())
    self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}

func listMonitor(monitor: ListMonitor<ListEntityType>,
                 didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath) {
    tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}

func listMonitor(monitor: ListMonitor<ListEntityType>,
                 didMoveObject object: ListEntityType,
                               fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
    tableView.moveRowAtIndexPath(fromIndexPath, toIndexPath: toIndexPath)
}
Originally created by @avshiyanov on GitHub (Sep 8, 2016). I have many sections on the list, for example 5, first one have only one element in section, when I do transation.delete(obj) App crashes. The same occurs if section does not exist and I do insert Crash CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null) Here is deletion logic: CoreStore.beginAsynchronous { (transaction) -> Void in transaction.delete(task) transaction.commit() } In data source I have: // MARK: ListObserver ``` func listMonitorWillChange(monitor: ListMonitor<ListEntityType>) { self.tableView.beginUpdates() } func listMonitorDidChange(monitor: ListMonitor<ListEntityType>) { self.tableView.endUpdates() } func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath) { print (indexPath) print (listMonitor.numberOfSections()) self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath) { print (indexPath) print (listMonitor.numberOfSections()) self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } func listMonitor(monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath) { tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None) } func listMonitor(monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { tableView.moveRowAtIndexPath(fromIndexPath, toIndexPath: toIndexPath) } ```
adam closed this issue 2025-12-29 15:23:45 +01:00
Author
Owner

@avshiyanov commented on GitHub (Sep 8, 2016):

CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)

@avshiyanov commented on GitHub (Sep 8, 2016): CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)
Author
Owner

@JohnEstropia commented on GitHub (Sep 8, 2016):

@avshiyanov I'm guessing this is because of moveRowAtIndexPath and reloadRowsAtIndexPaths, which do not really work well with NSFetchResultsController's event logic.

You can check that UITableViews work well with the CoreStoreDemo app. You'll see the main difference here:

    func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) {

        if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {

            let palette = Static.palettes[indexPath]
            cell.colorView?.backgroundColor = palette.color
            cell.label?.text = palette.colorText
        }
    }

    func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) {

        self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
        self.tableView.insertRows(at: [toIndexPath], with: .automatic)
    }

For updates, query the cell instance from the tableView and update it directly. For moves, delete the row first then re-insert.

@JohnEstropia commented on GitHub (Sep 8, 2016): @avshiyanov I'm guessing this is because of `moveRowAtIndexPath` and `reloadRowsAtIndexPaths`, which do not really work well with `NSFetchResultsController`'s event logic. You can check that UITableViews work well with the CoreStoreDemo app. You'll see the main difference here: ``` swift func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) { if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell { let palette = Static.palettes[indexPath] cell.colorView?.backgroundColor = palette.color cell.label?.text = palette.colorText } } func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) { self.tableView.deleteRows(at: [fromIndexPath], with: .automatic) self.tableView.insertRows(at: [toIndexPath], with: .automatic) } ``` For updates, query the cell instance from the tableView and update it directly. For moves, delete the row first then re-insert.
Author
Owner

@avshiyanov commented on GitHub (Sep 8, 2016):

@JohnEstropia When I comment moveRowAtIndexPath and reloadRowsAtIndexPaths I have the same issue, problem still persists on insertion/deletion for Sections only

@avshiyanov commented on GitHub (Sep 8, 2016): @JohnEstropia When I comment moveRowAtIndexPath and reloadRowsAtIndexPaths I have the same issue, problem still persists on insertion/deletion for Sections only
Author
Owner

@avshiyanov commented on GitHub (Sep 8, 2016):

monitorSectionedList: func buildListMonitorByTime(withPredicate predicate: NSPredicate?) ->
ListMonitor {
return CoreStore.monitorSectionedList(
From(TaskRecord),
SectionBy("dateName"),
OrderBy(.Descending("createdAt")),
Tweak { (fetchRequest) -> Void in
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
})
}

@avshiyanov commented on GitHub (Sep 8, 2016): monitorSectionedList: func buildListMonitorByTime(withPredicate predicate: NSPredicate?) -> ListMonitor<NSManagedObject> { return CoreStore.monitorSectionedList( From(TaskRecord), SectionBy("dateName"), OrderBy(.Descending("createdAt")), Tweak { (fetchRequest) -> Void in let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: false) fetchRequest.sortDescriptors = [sortDescriptor] }) }
Author
Owner

@avshiyanov commented on GitHub (Sep 8, 2016):

@JohnEstropia Found mistake on my side, forgot implement ListSectionObserver, without it didInsertSection, didDeleteSection does not call for me. Closed

@avshiyanov commented on GitHub (Sep 8, 2016): @JohnEstropia Found mistake on my side, forgot implement ListSectionObserver, without it didInsertSection, didDeleteSection does not call for me. Closed
Author
Owner

@JohnEstropia commented on GitHub (Sep 8, 2016):

Glad you worked it out :)

@JohnEstropia commented on GitHub (Sep 8, 2016): Glad you worked it out :)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#76