Search code examples
objective-ciosxcodecore-datansfetchedresultscontroller

NSFetchedResultsController & UITableViewController not communicating


I have some "details" saved into my Core Data, and I'm trying to call it out from an NSFetchedResultsController into my tableView. For whatever reason, the tableView isn't populating once the block is finished running.

I have been searching and asking everywhere I can to try and figure out this whole Core Data debacle. Hopefully, someone on here is kind enough to help me out!

HomeViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.navigationItem.title = @"Home";
    self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0 green:0.7 blue:2.3 alpha:1];

    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCompose target:self action:@selector(addShindy:)];

    self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"light_alu.png"]];
    self.tableView.opaque = NO;
    self.tableView.backgroundView = nil;

    UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
    [refreshControl addTarget:self action:@selector(refresh:)
             forControlEvents:UIControlEventValueChanged];
    self.refreshControl = refreshControl;

    [self setShindyDatabase:self.shindyDatabase];

}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    if (!self.shindyDatabase) {
        NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
        self.shindyDatabase = [[UIManagedDocument alloc] initWithFileURL:url];
    }
}

- (void)refresh:(UIRefreshControl *)sender
{
    [self useDocument];
    [sender endRefreshing];
}

- (void)addShindy:(UIBarButtonItem *)sender
{
    AddShindyViewController *addShindyViewController = [[AddShindyViewController alloc] initWithNibName:@"AddShindyViewController" bundle:nil];
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addShindyViewController];
    [self presentViewController:navController animated:YES completion:nil];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma Core Date Stack

- (void)fetchShindyDataIntoDocument:(UIManagedDocument *)document
{
    dispatch_queue_t fetchIntoDocument = dispatch_queue_create("Fetch Into Document", nil);
    dispatch_async(fetchIntoDocument, ^{
        NSArray *shindys = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        for (NSDictionary *shindyInfo in shindys) {
            [Shindy shindyWithShindyDBInfo:shindyInfo inManagedObjectContext:document.managedObjectContext];
        }
        [document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
    });
}

- (void)setShindyDatabase:(UIManagedDocument *)shindyDatabase
{
    if (_shindyDatabase != shindyDatabase) {
        _shindyDatabase = shindyDatabase;
        [self useDocument];
    }
}

- (void)useDocument
{
    NSError *error = nil;

    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.shindyDatabase.fileURL path]]) {
        NSLog(@"Create document");
        [self.shindyDatabase saveToURL:self.shindyDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            [self setupFetchedResultsController];
            [self fetchShindyDataIntoDocument:self.shindyDatabase];

            if (!success) {
                NSLog(@"error for creation of document: %@", [error localizedDescription]);
            }
        }];
    } else if (self.shindyDatabase.documentState == UIDocumentStateClosed) {
        NSLog(@"Closed document");
        [self.shindyDatabase.managedObjectContext.parentContext performBlock:^{
            [self setupFetchedResultsController];
        }];
    } else if (self.shindyDatabase.documentState == UIDocumentStateNormal) {
        NSLog(@"Normal Document");
        [self setupFetchedResultsController];
    }

    if (error) {
        NSLog(@"Error in useDocument: %@", [error localizedDescription]);
    }
}

- (void)setupFetchedResultsController
{
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Shindy"];
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"details" ascending:YES];
    request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    // request.predicate = [NSPredicate predicateWithFormat:@"details = %@", [self.shindyDatabase valueForKey:@"details"]];
    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                            managedObjectContext:self.shindyDatabase.managedObjectContext
                                                                              sectionNameKeyPath:nil
                                                                                    cacheName:nil];
}

#pragma mark - Table view data source

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 75;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }

    Shindy *shindy = [self.fetchedResultsController objectAtIndexPath:indexPath];
    NSLog(@"%@", shindy.details);
    cell.textLabel.text = shindy.details;

Shindy+CreateDB.m

+ (Shindy *)shindyWithShindyDBInfo:(NSDictionary *)shindyInfo
            inManagedObjectContext:(NSManagedObjectContext *)context
{
    Shindy *shindy = nil;

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Shindy"];

    // NSSortDescriptor *dateAndTimeSort = [NSSortDescriptor sortDescriptorWithKey:@"dateAndTime" ascending:YES];
    NSSortDescriptor *detailsSort = [NSSortDescriptor sortDescriptorWithKey:@"details" ascending:YES];
    // NSSortDescriptor *locationSort = [NSSortDescriptor sortDescriptorWithKey:@"location" ascending:YES];
    // NSSortDescriptor *nameSort = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    // NSSortDescriptor *photoSort = [NSSortDescriptor sortDescriptorWithKey:@"photo" ascending:YES];
    // NSSortDescriptor *timePostedSort = [NSSortDescriptor sortDescriptorWithKey:@"timePosted" ascending:YES];
    // title
    request.sortDescriptors = [NSArray arrayWithObject:detailsSort];
    // request.sortDescriptors = [NSArray arrayWithObjects:dateAndTimeSort, detailsSort, locationSort, nameSort, photoSort, timePostedSort, nil];

    NSError *error = nil;
    NSArray *matches = [context executeFetchRequest:request error:&error];

    if (error) {
        NSLog(@"document failed in file: %@", [error localizedDescription]);
    }

    if (!matches || ([matches count] > 1)) {
        NSError *error = nil;
        NSLog(@"error in DBInfo: %@", [error localizedDescription]);
    } else if ([matches count] == 0) {
        shindy = [NSEntityDescription insertNewObjectForEntityForName:@"Shindy" inManagedObjectContext:context];

        // shindy.dateAndTime = [shindyInfo objectForKey:@"dateAndTime"];
        shindy.details = [shindyInfo objectForKey:@"details"];
        // shindy.location = [shindyInfo objectForKey:@"location"];
        // shindy.name = [shindyInfo objectForKey:@"name"];
        // shindy.photo = [shindyInfo objectForKey:@"photo"];
        // shindy.timePosted = [shindyInfo objectForKey:@"timePosted"];
        // title
        // Guestlist? The rest?
        // Use below for reference
        // shindy.whoseShindy = [User userWithName:[shindyInfo objectForKey:@"whoseShindy"] inManagedObjectContext:context];
    } else {
        shindy = [matches lastObject];
    }

    return shindy;
}

EDIT:

I suppose I should have also shown a file of which I have set as a subclass of my HomeViewController. It's basically the same thing as the code that is given to you to paste in the Apple Documentation

CoreDataTableViewController.h

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface CoreDataTableViewController : UITableViewController <NSFetchedResultsControllerDelegate>

@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

- (void)performFetch;

@property (nonatomic) BOOL suspendAutomaticTrackingOfChangesInManagedObjectContext;

@property BOOL debug;

@end

CoreDataTableViewController.m

@interface CoreDataTableViewController()
@property (nonatomic) BOOL beganUpdates;
@end

@implementation CoreDataTableViewController

#pragma mark - Properties

@synthesize fetchedResultsController = _fetchedResultsController;
@synthesize suspendAutomaticTrackingOfChangesInManagedObjectContext = _suspendAutomaticTrackingOfChangesInManagedObjectContext;
@synthesize debug = _debug;
@synthesize beganUpdates = _beganUpdates;

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

#pragma mark - Fetching

- (void)performFetch
{
    if (self.fetchedResultsController) {
        if (self.fetchedResultsController.fetchRequest.predicate) {
            if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
        } else {
            if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
        }
        NSError *error;
        [self.fetchedResultsController performFetch:&error];
        if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
    } else {
        if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
    }
    [self.tableView reloadData];
}

- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
    NSFetchedResultsController *oldfrc = _fetchedResultsController;
    if (newfrc != oldfrc) {
        _fetchedResultsController = newfrc;
        newfrc.delegate = self;
        if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
            self.title = newfrc.fetchRequest.entity.name;
        }
        if (newfrc) {
            if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
            [self performFetch]; 
        } else {
            if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
            [self.tableView reloadData];
        }
    }
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [self.fetchedResultsController sectionIndexTitles];
}

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) {
        [self.tableView beginUpdates];
        self.beganUpdates = YES;
    }
}

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;

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


- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{       
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        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.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

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

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

- (void)endSuspensionOfUpdatesDueToContextChanges
{
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}

- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
{
    if (suspend) {
        _suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
    } else {
        [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
    }
}

@end

And then last, but not least, I have the header file for my HomeViewController of which I implement CoreDataTableViewController. Hopefully this is useful in better explaining what I've got.

HomeViewController.h

#import <UIKit/UIKit.h>
#import <FacebookSDK/FacebookSDK.h>
#import "CoreDataTableViewController.h"

@interface HomeViewController : CoreDataTableViewController

@property (strong, nonatomic) UIManagedDocument *shindyDatabase;
@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;

@end

Solution

  • I ended up figuring this, and many other Core Data issues I was having. I wasn't setting my managed object context to a store!! DUH!!!

    self.managedObjectContext = [(AppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext];
    

    That did the trick. Everything began working after that.

    I hope this is able to point out another duh to anybody out there forgetting the same thing I did!