Skip to content

Instantly share code, notes, and snippets.

@drale2k
Created April 10, 2013 22:40
Show Gist options
  • Save drale2k/5359100 to your computer and use it in GitHub Desktop.
Save drale2k/5359100 to your computer and use it in GitHub Desktop.

Revisions

  1. drale2k created this gist Apr 10, 2013.
    55 changes: 55 additions & 0 deletions CoreDataTableViewController.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    //
    // CoreDataTableViewController.h
    //
    // Created for Stanford CS193p Winter 2013.
    // Copyright 2013 Stanford University. All rights reserved.
    //
    // This class mostly just copies the code from NSFetchedResultsController's documentation page
    // into a subclass of UITableViewController.
    //
    // Just subclass this and set the fetchedResultsController.
    // The only UITableViewDataSource method you'll HAVE to implement is tableView:cellForRowAtIndexPath:.
    // And you can use the NSFetchedResultsController method objectAtIndexPath: to do it.
    //
    // Remember that once you create an NSFetchedResultsController, you CANNOT modify its @propertys.
    // If you want new fetch parameters (predicate, sorting, etc.),
    // create a NEW NSFetchedResultsController and set this class's fetchedResultsController @property again.
    //

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

    @interface CoreDataTableViewController : UIViewController <NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate>

    @property (strong, nonatomic) UITableView *tableView;

    // The controller (this class fetches nothing if this is not set).
    @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

    // Causes the fetchedResultsController to refetch the data.
    // You almost certainly never need to call this.
    // The NSFetchedResultsController class observes the context
    // (so if the objects in the context change, you do not need to call performFetch
    // since the NSFetchedResultsController will notice and update the table automatically).
    // This will also automatically be called if you change the fetchedResultsController @property.
    - (void)performFetch;

    // Turn this on before making any changes in the managed object context that
    // are a one-for-one result of the user manipulating rows directly in the table view.
    // Such changes cause the context to report them (after a brief delay),
    // and normally our fetchedResultsController would then try to update the table,
    // but that is unnecessary because the changes were made in the table already (by the user)
    // so the fetchedResultsController has nothing to do and needs to ignore those reports.
    // Turn this back off after the user has finished the change.
    // Note that the effect of setting this to NO actually gets delayed slightly
    // so as to ignore previously-posted, but not-yet-processed context-changed notifications,
    // therefore it is fine to set this to YES at the beginning of, e.g., tableView:moveRowAtIndexPath:toIndexPath:,
    // and then set it back to NO at the end of your implementation of that method.
    // It is not necessary (in fact, not desirable) to set this during row deletion or insertion
    // (but definitely for row moves).
    @property (nonatomic) BOOL suspendAutomaticTrackingOfChangesInManagedObjectContext;

    // Set to YES to get some debugging output in the console.
    @property BOOL debug;

    @end
    170 changes: 170 additions & 0 deletions CoreDataTableViewController.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,170 @@
    //
    // CoreDataTableViewController.m
    //
    // Created for Stanford CS193p Winter 2013.
    // Copyright 2013 Stanford University. All rights reserved.
    //

    #import "CoreDataTableViewController.h"

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

    @implementation CoreDataTableViewController

    #pragma mark - Properties

    - (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
    {
    NSLog(@"Changed: %@", anObject);
    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];
    NSLog(@"did change");
    }

    - (void)endSuspensionOfUpdatesDueToContextChanges
    {
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
    }

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

    @end