views:

40

answers:

1

Hi,

I am working with a UITableView that gets its data from an NSFetchedResultsController. I would like to add a UISegmentedController to my navigation bar that would toggle the table between displaying all of the records and only the records where starred == YES.

I have read some other SO posts indicating that one way to do this is to create a second NSFetchedResultsController that has an NSPredicate with starred == YES, but it seems awfully overkill to create a second NSFetchedResultsController.

Is there a simpler way to do this?

+1  A: 

Not according to the docs on NSFetchedResultsController. If you take a look at the documentation on the fetchRequests property, you'll see the following note:

Important: You must not modify the fetch request. For example, you must not change its predicate or the sort orderings.

Since the fetchRequest property is read-only, the only option is creating a new fetched results controller.

It might be possible to change the predicate and have things work, but it's generally a bad idea to do stuff that goes explicitly against things Apple says in the documentation, because it could break in a future release.

And, beware of premature optimization! Unless you've tested it out and found out that creating a whole new fetched results controller is a big performance drain, it's not worth trying to do something in a non-recommended way.

Here's how I set a new predicate on my fetched results controller. fetchedResultsController is a property of my view controller. predicate is a private ivar of the view controller.

I already had all of the code for creating the fetched results controller on demand, so to set the predicate it's just a matter of deleted the cached one.

- (void)setPredicate:(NSPredicate *)newPredicate {
    predicate = [newPredicate copy];

    // Make sure to delete the cache
    // (using the name from when you created the fetched results controller)
    [NSFetchedResultsController deleteCacheWithName:@"Root"];

    // Delete the old fetched results controller
    self.fetchedResultsController = nil;

    // TODO: Handle error!
    // This will cause the fetched results controller to be created
    // with the new predicate
    [self.fetchedResultsController performFetch:nil];
    [self.tableView reloadData];
}

This code if based on the boilerplate XCode generates when you start a project that uses Core Data.

- (NSFetchedResultsController *)fetchedResultsController {

    if (fetchedResultsController != nil) {
        return fetchedResultsController;
    }

    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:[MyEntity entityName] inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];

    [fetchRequest setFetchBatchSize:20];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];
    [fetchRequest setPredicate:predicate];

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

    [aFetchedResultsController release];
    [fetchRequest release];
    [sortDescriptor release];
    [sortDescriptors release];

    return fetchedResultsController;
}
Jacques
I would just create two separate FRC and assign them to different properties of controller. Then depending on which toggle is active, the table would use one or the other.
TechZen