Bartłomiej Semańczyk Bartłomiej Semańczyk - 5 years ago 1017
Swift Question

iOS 9 - "attempt to delete and reload the same index path"

This is an error:


CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete and reload the same index path ( {length = 2, path = 0 - 0}) with userInfo (null)


This is my typical
NSFetchedResultsControllerDelegate
:

func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {

let indexSet = NSIndexSet(index: sectionIndex)

switch type {
case .Insert:
tableView.insertSections(indexSet, withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(indexSet, withRowAnimation: .Fade)
case .Update:
fallthrough
case .Move:
tableView.reloadSections(indexSet, withRowAnimation: .Fade)
}
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

switch type {
case .Insert:
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
case .Move:
if let indexPath = indexPath {
if let newIndexPath = newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
}
}
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}


in
viewDidLoad()
:

private func setupOnceFetchedResultsController() {

if fetchedResultsController == nil {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchReguest = NSFetchRequest(entityName: "DBOrder")
let dateDescriptor = NSSortDescriptor(key: "date", ascending: false)

fetchReguest.predicate = NSPredicate(format: "user.identifier = %@", DBAppSettings.currentUser!.identifier )
fetchReguest.sortDescriptors = [dateDescriptor]
fetchReguest.fetchLimit = 10
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReguest, managedObjectContext: context, sectionNameKeyPath: "identifier", cacheName: nil)
fetchedResultsController.delegate = self

try! fetchedResultsController.performFetch()
}
}

Answer Source

For some reason NSFetchedResultsController calls .Update followed by .Move after controllerWillChangeContent: is called.

Simply it looks like this: BEGIN UPDATES -> UPDATE -> MOVE -> END UPDATES.

Happens only under iOS 8.x

During one session of update the same cell is reloaded and deleted what cause a crash.

THE SIMPLEST FIX EVER:

The following part of code:

case .Update:
    if let indexPath = indexPath {
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    }

replace with:

case .Update:
    if let indexPath = indexPath {

        // 1. get your cell
        // 2. get object related to your cell from fetched results controller
        // 3. update your cell using that object

        //EXAMPLE:
        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? WLTableViewCell { //1
            let wishlist = fetchedResultsController.objectAtIndexPath(indexPath) as! WLWishlist //2
            cell.configureCellWithWishlist(wishlist) //3
        }
    }

THAT REALLY WORKS.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download