Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delete row in table view with fetchedResultController

During swype deleting (most importatnt lines of this method):

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
 if (editingStyle == UITableViewCellEditingStyleDelete)
  {
    Table *deleteRow = [self.fetchedResultsController objectAtIndexPath:indexPath];
    [self.managedObjectContext deleteObject:deleteRow];
    [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
  }   
}

When deleting row I get this error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 2.
The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), 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).'

If I comment last line of code ([tableView deleteRowsAtIndexPaths:...]) everything works fine (but I have to refresh view to see that row was deleted).

How to do it properly..?

EDIT: Considering @Kyr Dunenkoff responce I've added:

- (void)controller:(NSFetchedResultsController*)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath*)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath*)newIndexPath
{
    UITableView *tableV = [self tableView];
    switch(type) {
        case NSFetchedResultsChangeDelete:
            [tableV deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller
{
    [[self tableView] endUpdates];
}

- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller
{
    [[self tableView] beginUpdates];
}

However this didn't change the crashing & error. Atm it just caused that adding new rows isn't working any more.

like image 493
Nat Avatar asked Jan 23 '12 16:01

Nat


4 Answers

To do this properly, implement controller:didChangeObject: method, within it make a switch for different change types, within NSFetchedResultsChangeDelete case insert your [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];. Because when you work with data source like NSFetchedResultsController, all changes must come from there and your table only reflects them. Additionally, you can implement controller:willChangeContent and controller:didChangeContent, put [tableView beginUpdates] into willChange and [tableView endUpdates] into didChange.

like image 113
Kyr Dunenkoff Avatar answered Nov 14 '22 11:11

Kyr Dunenkoff


The problem is - as you've discovered by commenting it out - your last line:

[tableView deleteRowsAtIndexPaths:...

The apple Table View Programming Guide tells you need to do that, but in fact you're not supposed to when using NSFetchedResultsController. The idea of NSFetchedResultsController is that it takes care of updating the UI to keep it in sync with the model so that you don't need to. Hence, if you simply delete the object from the model, the fetched results controller will take care of the rest.

[Update] Whoops, not entirely correct what I said just there. Fetched results controller will not just "take care of the rest" without help. It's delegate will take care of the rest, but someone needs to implement it's delegate. I'm in the habit of including the CoreDataTablewViewController helper class from the Standford CS193p course, which is simply a UITableViewController subclass that implements all of the methods recommended in the documentation of NSFetchedResultsController. Most importantly, it implements the delegate methods that update the table view when the model changes.

So your updated approach is still correct: you should NOT put the deleteRowsAtIndexPaths.. in the code that causes the user to delete them, but let the fetched results controller drive the process, and implement the delegate to actually do it.

(get the helper class here: http://www.stanford.edu/class/cs193p/cgi-bin/drupal/downloads-2011-fall, and watch lecture 14 to see how to use it)

like image 45
Rhubarb Avatar answered Nov 14 '22 10:11

Rhubarb


Kyr's answer is correct, but here is a discrete example of how to delete an NSManagedObject from your database as well as from from the NSFetchedResultsController:

- (void) deleteObjectFromDBAndTable:(NSIndexPath *)indexPath forTable:(UITableView*)tableView
{
    NSLog(@"Deleting object at row %d", indexPath.row);

    // Delete the object from the data source
    NSManagedObject *objToDelete = (NSManagedObject*)[self.fetchedResultsController objectAtIndexPath:indexPath];

    // Delete the object from the database
    [DBHandler deleteObject:objToDelete andSave:YES];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    switch (type)
    {
        case NSFetchedResultsChangeDelete:

            [_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

            break;
        default:
            break;
    }
}
like image 44
DiscDev Avatar answered Nov 14 '22 11:11

DiscDev


You need to make sure you don't call [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; yourself. Deleting the object from the context will trigger the NSFetchedResultsController to refresh itself AND the table.

like image 20
mprivat Avatar answered Nov 14 '22 12:11

mprivat