3

I have a UITableView that displays a subset of a large number of entities named "Documents". The subset is defined by another entity "Selection". Selections are named, ordered list of documents.

It Works fine, except when I want to change the displayed selection at run time. I get only a blank list.

Basically, I need to change the predicate that my NSFetchedResultsController holds so that the new predicate uses the another Selection. I couldn't make it work. My last attempt is to get rid of the NSFetchedResultsController altogether and reallocate it:

- (void) displaySelection:(Selection *)aSet
{
 self.currentSelection = aSet;
 self.fetchedResultsController = nil;
 // methods here don't all use the property but directly the ivar, so we must trigger the getter
 [self fetchedResultsController];
 [self.tableView reloadData];
}

And of course, the NSFetchedResultsController getter does the right thing:

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

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"DocInSelection" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"selection.identifier like %@", currentSelection.identifier];
    [fetchRequest setPredicate:predicate];
<snip>
    [fetchRequest setSortDescriptors:sortDescriptors];
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
<snip>
    return fetchedResultsController;
}

This code works the first time, because the Initial Selection is set. When displaySelection: is called, though, the tableview becomes blank.

A very similar question was asked at NSFetchedResultsController fetch request - updating predicate and UITableView

And the answer was to get rid of the NSFetchedResultsController. I don't want to do that, because NSFetchedResultsController brings a lot of useful goodies here (eg caching, partial loading...). The question still stands: how to "switch" data in a UITableView backed by a NSFetchedResultsController, where "switch" means having a different predicate, or even (not in my case) a different entity.

Note for the sake of completeness, that since the many-to-many relationship from Selection to Document is ordered, it is handled through an in-between lightweight entity called DocInSelection, which has an "ordering" property and two many-to-one relationships to Document and Selection.

Thanks for any suggestion.

Community
  • 1
  • 1
Jean-Denis Muys
  • 6,772
  • 7
  • 45
  • 71

4 Answers4

15

Since NSFetchedResultsController(FRC) is an object, you can store instances of it like any other object.

One useful technique is to initialize and store several FRC in a dictionary and then set the tableview controller's fetchedResultController attribute to the FRC you need at the moment. This is useful for situations such as having a segmented control to sort on different attributes or entities in the same table. This technique has the advantage of maintaining the individual FRC caches which can speed fetches up significantly.

Just make sure to send the tableview itself a beginUpdates before you swap controllers and then an endUpdates when you are done. This prevents the table from asking for data in the narrow window when the FRC are being swapped out. Then call reloadData.

TechZen
  • 64,370
  • 15
  • 118
  • 145
  • ah ah ! interesting. Since the set of selections depends on user action, it's probably not quite the right idea for me. I'll keep in mind though. Thanks. – Jean-Denis Muys Aug 16 '10 at 16:14
  • 1
    How would you do the beginUpdate/endUpdates on a UICollectionView since it doesn't have corresponding methods? – Kudit Jul 30 '13 at 18:51
6

After I posted my question, I tried a variant of the code the OP of the other question showed. It works for me. Here it is:

- (void) displaySelection:(Selection *)aSet
{
    if (aSet != self.currentSelection) {
        self.currentSelection = aSet;

        NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
        NSPredicate *predicate = nil;
        NSEntityDescription *entity = nil;
        entity = [NSEntityDescription entityForName:@"DocInSelection" inManagedObjectContext:managedObjectContext];
        predicate = [NSPredicate predicateWithFormat:@"selection.identifier like %@", currentSelection.identifier];
        [fetchRequest setEntity:entity];
        [fetchRequest setPredicate:predicate];

        [NSFetchedResultsController deleteCacheWithName:@"Root"];

        NSError *error = nil;
        if (![[self fetchedResultsController] performFetch:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }       
    }
    [self.tableView reloadData];
}
Jean-Denis Muys
  • 6,772
  • 7
  • 45
  • 71
  • 1
    If you're going to deleteCacheWithName:@"Root" every time, why even create the cache. Pass in a cache name of nil if your not using the cache. Although, caching is one of the benefits of using the NSFRC in the first place. – bandejapaisa Aug 15 '11 at 13:05
  • I suppose that while the current selection (which might be huge) is left unchanged, the cache is used by the `NSFetchedResultController` to good effect. "Every time" might not be that often. Also Apple says in [Modifying the Fetch Request](http://developer.apple.com/library/iOS/#documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html) "If you are using a cache, delete it (using deleteCacheWithName:)." Apple also adds "Typically you should not use a cache if you are changing the fetch request". Testing should help decide whether the cache is beneficial here. – Jean-Denis Muys Aug 18 '11 at 08:17
3

While this may work there's a note in the iOS Reference Library that troubles me:

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

Source: NSFetchedResultsController Class Reference

This additional note doesn't exist in the iOS 3.2 Reference Library.

Just wanted to point this out.

Michael Thiel
  • 2,434
  • 23
  • 21
  • 1
    I hadn't noticed that note. It would be best if we could get an explanation from Apple on that. I sort of understand it: since the FetchedResultController keeps its fetch results around, pulling its fetchRequest rug from under its feet could lead to inconsistencies. That's why the code snippet above 1- clears the cache, 2- performs the fetch request again, and 3- asks the tableView to reload its data. It seems ok for now, but I'd like to have some official word on that. – Jean-Denis Muys Sep 16 '10 at 14:03
0

An important note: if you "overwrite" a fetchController object make sure you clear its .delegate first - otherwise you'll get crashes when deleting rows, etc as the old fetchController and its delegate get events.

Jason
  • 1,323
  • 14
  • 19