Alex Stone
Alex Stone

Reputation: 47354

iPhone4 iOS5 NSFetchedResultsController how to set the delegate properly in a subclass?

I have a parent class that handles all the UITableview persistent data management and UITableView row managemetn. I've copied most of the code from a new XCode4 project with persistent data for a UITableview. Now I'm trying to delete a tablerow in a child class, and none of the delegate methods get called when a row is deleted, causing my tableview to be left in an inconsistent state.

Am I assigning the delegate correctly? Who's the delegate of the NSFetechedResultsController in this case? Do I need to explicitly make my child class a NSFetchedResultsControllerDelegate?

Here's how I retrieve the NSFetchedResultsController, note how it assigns self as the delegate.

- (NSFetchedResultsController *)fetchedResultsController
{
    if (__fetchedResultsController != nil)
    {
        return __fetchedResultsController;
    }

    /*
     Set up the fetched results controller.
    */
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self.managedObjectContext];

    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:fetchBatchSize];

    // Edit the sort key as appropriate.
//    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"startMinute" ascending:YES];
    NSArray *sortDescriptors;
    if(sortDescriptor2!=nil)
    {
     sortDescriptors= [[NSArray alloc] initWithObjects:sortDescriptor,sortDescriptor2, nil];
    }else
    {
     sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    }
    [fetchRequest setSortDescriptors:sortDescriptors];

    if(filterPredicate != nil)
    {
        [fetchRequest setPredicate:filterPredicate];
    }

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;



    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error])
        {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
//      abort();
    }

    return __fetchedResultsController;
}  

When it's time to delete table rows, none of the delegate methods get invoked, even thought the table row is really deleted. To debug the issue, I added the following 2 assertions. The second assertion fails.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    id delegate = self.fetchedResultsController.delegate;


    NSAssert(delegate!=nil,@"Tableview is not delegate of fetched results controller!");

//this assertion fails
    NSAssert([((UITableViewController*)self) isEqual:delegate],@"Tableview is not delegate of fetched results controller!");

    if (editingStyle == UITableViewCellEditingStyleDelete)
    {


        // Delete the managed object for the given index path
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];

        [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

        // Save the context.
        NSError *error = nil;
        if (![context save:&error])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
//            abort();
        }
    }   
}

Thank you !

Upvotes: 0

Views: 1198

Answers (2)

d.ennis
d.ennis

Reputation: 3455

You have to use these methods for the NSFetchedResultsController to be able to react for changes in your table view:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
{
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            // your code for insert
        break;
        case NSFetchedResultsChangeDelete:
           // your code for deletion
        break;
    }
}

- (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:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
        break;

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

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];
}

Also make the ViewController using the NSFetchedResultsController listen to the NSFetchedResultsControllerDelegate.

Upvotes: 1

XJones
XJones

Reputation: 21967

The code setting up the NSFetchedResultsController looks correct. It's delegate set to self means it's delegate is the object you are creating it in. This looks like the same object that is the delegate for the tableView. It also looks like you correctly deleting the object and saving the context.

What you don't show and is likely the cause of the crash is your handling of the NSFetchedResultsController delegate methods. At a minimum, you need to add the following:

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

This will force the table to reload whenever the fetchedResultsController updates itself when it detects changes in its managedObjectContext.

If this is not your issue then you need to post some more code showing how you have your classes configured and how you are handling the fetchedResultsController delegate methods.

EDIT: fixed incorrect signature om controllerDidChangeContent:

Upvotes: 1

Related Questions