Can't delete table row at index after delete record in Core Data

Viewed 967

I'll start out by saying that I am VERY new to making apps in Xcode / Swift. I've never really done anything with Obj-C so Swift is really my first entry into coding for iOS.

My problem is that I am attempting to delete the table cell after deleting the Core Data record. I seem to be able to delete the Core data just fine, but get the same error every time I try to delete the cell row.

The error I'm getting is as follows:

2016-03-10 08:49:56.484 EZ List[31764:3225607] * Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/UITableView.m:1720 2016-03-10 08:49:56.487 EZ List[31764:3225607] * 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).' [... And so on]

And the code I'm using to delete is this:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {

    let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
        // delete item at indexPath
        let request = self.fetchRequest()
        var fetchResults = [AnyObject]()

        do {
            fetchResults = try self.moc.executeFetchRequest(request)
        } catch {
            fatalError("Fetching Data to Delete Failed")
        }

        self.moc.deleteObject(fetchResults[indexPath.row] as! NSManagedObject)

        fetchResults.removeAtIndex(indexPath.row)

        do {
            try self.moc.save()
        } catch {
            fatalError("Failed to Save after Delete")
        }

        self.lists.removeAtIndex(indexPath.row)
        self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)

    }

    let edit = UITableViewRowAction(style: .Normal, title: "Edit") { (action, indexPath) in
        // edit item at indexPath

    }

    edit.backgroundColor = UIColor.init(red: 84/255, green: 200/255, blue: 214/255, alpha: 1)

    return [delete, edit]

}

After testing a bunch I have verified that the problem is with

self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)

But I don't understand why it thinks the rows in sections are invalid. Any help would much appreciated. So far all my searching has come up with Obj-C answers or swift answers that don't seem to fix my problem.

EDIT: Adding code for numberOfRowsInSection

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    if let sections = frc.sections {
        let currentSection = sections[section]
        return currentSection.numberOfObjects
    }

    return 0
}

EDIT2: Here is the full code for my ViewController **Keep in mind that I've been trying a few different things to get it to work so there are a few commented out things in there.

import UIKit
import CoreData

class ListTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
var lists : [Lists] = []

/*
var listNames: [String] = ["Any List", "Other List"]
var listItemCount: [Int] = [5, 6]
var listIcons: [UIImage] = [UIImage(named: "Generic List")!, UIImage(named: "Generic List")!]
*/

override func viewWillAppear(animated: Bool) {

    let imageView = UIImageView(image: UIImage(named: "TableBackground"))
    imageView.contentMode = .ScaleAspectFill
    self.tableView.backgroundView = imageView
    self.tableView.tableFooterView = UIView(frame: CGRectZero)
    do {
        self.lists = try moc.executeFetchRequest(fetchRequest()) as! [Lists]
    } catch {
        fatalError("Failed setting lists array to fetch request")
    }
    self.tableView.reloadData()

}

override func viewDidLoad() {
    super.viewDidLoad()

    checkAnyList()

    frc = getFCR()
    frc.delegate = self

    do {
        try frc.performFetch()
    } catch {
        fatalError("Failed to perform inital fetch")
    }

    self.tableView.reloadData()

    // Uncomment the following line to preserve selection between presentations
    //self.clearsSelectionOnViewWillAppear = true

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem()
}

override func viewDidAppear(animated: Bool) {

    checkAnyList()

    frc = getFCR()
    frc.delegate = self

    do {
        try frc.performFetch()
    } catch {
        fatalError("Failed to perform inital fetch")
    }

    self.tableView.reloadData()

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    if let sections = frc.sections {
        return sections.count
    }

    return 0
}

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    cell.backgroundColor = UIColor.clearColor()

}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    if let sections = frc.sections {
        let currentSection = sections[section]
        return currentSection.numberOfObjects
    }

    return 0
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let listCellReturn = tableView.dequeueReusableCellWithIdentifier("listCell", forIndexPath: indexPath) as! ListTableViewCell

    //let list = frc.objectAtIndexPath(indexPath) as! Lists
    let list = self.lists[indexPath.row]

    listCellReturn.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)

    listCellReturn.listNameLabel.text = list.name
    listCellReturn.listIconImage.image = UIImage(data: (list.image)!)

    /*
    listCellReturn.listNameLabel.text = listNames[indexPath.row]
    listCellReturn.itemCountLabel.text = "Items: \(listItemCount[indexPath.row])"
    listCellReturn.listIconImage.image = listIcons[indexPath.row]
    */

    return listCellReturn
}

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {

    let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
        // delete item at indexPath
        let request = self.fetchRequest()
        var fetchResults = [AnyObject]()

        do {
            fetchResults = try self.moc.executeFetchRequest(request)
        } catch {
            fatalError("Fetching Data to Delete Failed")
        }

        self.moc.deleteObject(fetchResults[indexPath.row] as! NSManagedObject)

        fetchResults.removeAtIndex(indexPath.row)

        do {
            try self.moc.save()
        } catch {
            fatalError("Failed to Save after Delete")
        }

        self.lists.removeAtIndex(indexPath.row)
        self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)

    }

    let edit = UITableViewRowAction(style: .Normal, title: "Edit") { (action, indexPath) in
        // edit item at indexPath

    }

    edit.backgroundColor = UIColor.init(red: 84/255, green: 200/255, blue: 214/255, alpha: 1)

    return [delete, edit]

}

func fetchRequest() -> NSFetchRequest {

    let fetchRequest = NSFetchRequest(entityName: "Lists")
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]

    return fetchRequest

}

func getFCR() -> NSFetchedResultsController {

    frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)

    return frc

}

func checkAnyList() {

    var exists: Bool = false

    let fetchReq = NSFetchRequest(entityName: "Lists")
    let pred = NSPredicate(format: "%K == %@", "name", "Any List")
    fetchReq.predicate = pred

    do {
        let check = try moc.executeFetchRequest(fetchReq)
        for rec in check {
            if let name = rec.valueForKey("name") {
                if (name as! String == "Any List") {
                    exists = true
                }
            }
        }

        if (exists == false) {
            let entityDesc = NSEntityDescription.entityForName("Lists", inManagedObjectContext: moc)
            let list = Lists(entity: entityDesc!, insertIntoManagedObjectContext: moc)

            list.name = "Any List"
            list.image = UIImagePNGRepresentation(UIImage(named: "Any List")!)

            do {
                try moc.save()
            } catch {
                fatalError("New list save failed")
            }
        }
    } catch {
        fatalError("Error checking for Any List")
    }

        //let check = try moc.executeFetchRequest(fetchReq)
        //for rec in check {

        //} catch {

}

/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    // Return false if you do not want the specified item to be editable.
    return true
}
*/

/*
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        // Delete the row from the data source
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    } else if editingStyle == .Insert {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }    
}
*/

/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

}
*/

/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    // Return false if you do not want the item to be re-orderable.
    return true
}
*/

// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
2 Answers
Related