Question

This is a strange issue.

In my view controller SpieleOrtTVC I am going to present a subset of the objects of the entity Spiel. Every time the view controller is called a different subset should be displayed based on the users's selection within the presenting view controller.

This works just well for each first time the view controller is called. Depending on the user's selection, fetch creteria is handed over to the new view controller and actually arrives there correctly, as the NSLogs prove. The result and data displayed is as expeted.

But when the view controller is called a second or third time, then, too, the correct fetch criteria is handed to the view controller but aparenty the fetch result corresponds to the fetch that was performed earlier.

This is the code. SpieleOrtTVC is called out of a map callout. The selected object's name, which happens to be in the annotation's title, is handed over to the newly instanciated SpieleOrtTVC.

The calling view controller with a map:

- (void)mapView:(MKMapView *)mv annotationView:(MKAnnotationView *)pin calloutAccessoryControlTapped:(UIControl *)control {

    SpieleOrtTVC *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:STORYBOARD_ID_SPIELE_ORT];

    MKPointAnnotation *theAnnotation = (MKPointAnnotation *) pin.annotation;

    NSLog(@"the Annotation %@",theAnnotation.title);

    detailViewController.ortName = theAnnotation.title;
    detailViewController.stadionName = theAnnotation.subtitle;

    [self presentViewController:detailViewController animated:YES completion:nil];
}

SpieleOrteTVC.h:

@property (strong, nonatomic) NSString *ortName;
@property (strong, nonatomic) NSString *stadionName;

(Just a property, no getters or setters etc, auto-synthesized)

This is the piece of code that I doubt of SpieleOrteTVC.m:

- (NSManagedObjectContext *) managedObjectContext {

    if (! _managedObjectContext) {
        _managedObjectContext = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
    }

    return _managedObjectContext;
}

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

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

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

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:[self sortDescriptorString] ascending:[self sortAscending]];
    NSArray *sortDescriptors = @[sortDescriptor];

    [fetchRequest setSortDescriptors:sortDescriptors];

    [fetchRequest setPredicate:[self predicate]];

    // 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:@"Master"];
    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.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    NSLog(@"request: %@", fetchRequest);
    for (NSManagedObject *mo in [_fetchedResultsController fetchedObjects]) {
        NSLog(@"fetched: %@", [mo valueForKey:ATTRIB_ANSTOSS]);
    }


- (NSString *) entityName {

    return ENTITY_SPIEL;
}

- (NSString *) sortDescriptorString{

    return ATTRIB_ANSTOSS;
}

- (NSPredicate *) predicate {

    return [NSPredicate predicateWithFormat:@"(spielOrt.name == %@)", self.ortName];
}

- (BOOL) sortAscending {
    return YES;
}

Output of the first-time call:

2014-05-11 13:49:16.209 myApp[2745:60b] the Annotation Porto Alegre
2014-05-11 13:49:21.937 myApp[2745:60b] request: <NSFetchRequest: 0x18c0c960> (entity: Spiel; predicate: (spielOrt.name == "Porto Alegre"); sortDescriptors: ((
    "(anstoss, ascending, compare:)"
)); batch size: 20; type: NSManagedObjectResultType; )
2014-05-11 13:49:21.955 myApp[2745:60b] fetched: 2014-06-15 19:00:51 +0000
2014-05-11 13:49:21.957 myApp[2745:60b] fetched: 2014-06-18 16:00:52 +0000
2014-05-11 13:49:21.959 myApp[2745:60b] fetched: 2014-06-22 19:00:51 +0000
2014-05-11 13:49:21.960 myApp[2745:60b] fetched: 2014-06-25 16:00:51 +0000
2014-05-11 13:49:21.962 myApp[2745:60b] fetched: 2014-06-30 20:00:04 +0000

And this is the next call's result, with a different user selection:

2014-05-11 13:50:25.654 myApp[2745:60b] the Annotation Fortaleza
2014-05-11 13:50:25.675 myApp[2745:60b] request: <NSFetchRequest: 0x18c6c0e0> (entity: Spiel; predicate: (spielOrt.name == "Fortaleza"); sortDescriptors: ((
    "(anstoss, ascending, compare:)"
)); batch size: 20; type: NSManagedObjectResultType; )
2014-05-11 13:50:25.681 myApp[2745:60b] fetched: 2014-06-15 19:00:51 +0000
2014-05-11 13:50:25.683 myApp[2745:60b] fetched: 2014-06-18 16:00:52 +0000
2014-05-11 13:50:25.684 myApp[2745:60b] fetched: 2014-06-22 19:00:51 +0000
2014-05-11 13:50:25.686 myApp[2745:60b] fetched: 2014-06-25 16:00:51 +0000
2014-05-11 13:50:25.687 myApp[2745:60b] fetched: 2014-06-30 20:00:04 +0000

It shows clearly that the different selection criteria is taken correctly from the user's interaction and handed down to the new view controller. Especially the fetch request gets its predicate set accordingly but the results are the same in both cases. (the timestamp used for the sample here are unique for all objects)

What is wrong here?

I am happy to share more code. Just tell me what you think is relevant for the question.

If that is of importance: iOS 7.1.1, running on the device (iPhone 4, 4S, 5, iPad mini - all the same), Xcode 5.1.1

(I do have a workaround handy, but out of curiosity I'd like to know what the problem is.)

Was it helpful?

Solution

From the NSFetchedResultsController reference:

Important: If you are using a cache, you must call deleteCacheWithName: before changing any of the fetch request, its predicate, or its sort descriptors. You must not reuse the same fetched results controller for multiple queries unless you set the cacheName to nil.

So the problem in your case is that the FRC re-uses a cache that was created for a different predicate. Either don't use a cache (cacheName:nil) or delete the cache before the new FRC is created. In your situation, a cache probably does not make much sense.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top