App Dev Guy
App Dev Guy

Reputation: 5546

Edit TableView containing NSFetchedResults

When deleting an object from my Core Data that is fetched using NSFetchedResultsController and displayed in a TableView, it does not update the table. It deletes the object just fine however the row remains there until I swap views and return. I have noticed that this issue has only started happening since iOS8 but could be wrong. Below is my code:

#pragma mark - Fetched Results Controller Delegate

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {

    return YES;
}

// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

    if (editingStyle == UITableViewCellEditingStyleDelete) {

        NSManagedObjectContext * context = [self managedObjectContext];
        entity * rowToDelete = [self.fetchedResultsController objectAtIndexPath:indexPath];

        [context deleteObject:rowToDelete];

        //check that the row has deleted data
        NSLog(@"Shhiiiiiiiiii...... You done did delete a row...");

        NSError * error = nil;
        if (![context save:&error]){
            NSLog(@"Error: %@", error);
        }

        //causes a crash
        //[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

        //DOES NOT UPDATE THE TABLE        
        [self.tableView reloadData];
    }

}

I use all the normall delegates like so:

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

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

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

switch (type) {
    case NSFetchedResultsChangeInsert:
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationRight];
        break;
    case NSFetchedResultsChangeDelete:
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];
        break;

    case NSFetchedResultsChangeUpdate:{
        entity * details = [self.fetchedResultsController objectAtIndexPath:indexPath];
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        cell.textLabel.text = details.detailString;
    }
        break;

    case NSFetchedResultsChangeMove:
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    }

}

-(void) controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type{

switch (type) {
    case NSFetchedResultsChangeInsert:
        [self.tableView insertSections:[NSIndexSet indexSetWithIndex: sectionIndex] withRowAnimation:UITableViewRowAnimationRight];
        break;

    case NSFetchedResultsChangeDelete:
        [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationRight];
        break;

    case NSFetchedResultsChangeMove:
        NSLog(@"A table item was moved");
        break;
    case NSFetchedResultsChangeUpdate:
        NSLog(@"A table item was updated");
        break;

    }

}

I have searched on Stack and find the generic response "You need to use [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];". This does not work.

Thanks in advance for any help. If I do not respond immediately it's because I am taking a breather or asleep ;-)

Upvotes: 0

Views: 160

Answers (2)

App Dev Guy
App Dev Guy

Reputation: 5546

I stupidly placed

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

At the beginning before the actions could update. Rookie mistake. Hope it helps anyone else who falls for the same thing.

Upvotes: 0

button
button

Reputation: 656

NSFetchedResultsController should be able to communicate all the changes for you.

  • Check that the update functions are being called (i.e. narrow down where the failure might be)
  • Check that the NSFetchedResultsController instance has the delegate set
  • Check that you are working the the same, or connected, context (i.e. check that there is even a chance of the notification propagating)
  • I'm not sure if you can ignore re-ordering (might depend on your approach) but imagine that [object A, row 1] is swapped with [object B, row 2], then object B is deleted, how does the system know which table row to delete (unless you do something extra with the information)

Manually deleting a row from the table will cause a crash as the data source will be out of line with the table -- thus, causing all manner of confusion. The delegate methods for the results are there to enable the synchronisation of the actual results with those shown in the table.

I cut the following out of a working demo (although modified a bunch of stuff on the way). It works and receives changes, and updates the table. There are a few blog posts only a google away that will help too.

- (void)viewDidLoad {
    [super viewDidLoad];
    _fetchedResultsController = /* my fetched results controller... */;
    _fetchedResultsController.delegate = self;
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSInteger count = [[_fetchedResultsController sections] count];
    return count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
    NSInteger count = [sectionInfo numberOfObjects];
    return count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self configureCell:nil atIndexPath:indexPath];
}


- (UITableViewCell *)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath {
    id obj = [self.fetchedResultsController objectAtIndexPath:indexPath];
    if(!cell) {
        cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyTableViewCell" forIndexPath:indexPath];
    }
    cell.textLabel.text = obj.someProperty;
    return cell;
}

#pragma mark NSFetechResults delegate

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

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        default:
            break;
    }
}

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

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath]
                    atIndexPath:indexPath];
            break;
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
}

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

Upvotes: 1

Related Questions