Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kristopherjohnson/8257082 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/8257082 to your computer and use it in GitHub Desktop.
General-purpose implementation of NSFetchedResultsControllerDelegate
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
// Protocol adopted by an object that can configure a table view cell with data from an object
@protocol KDJTableViewCellConfigurationDelegate <NSObject>
- (void)configureCell:(UITableViewCell *)cell withObject:(NSObject *)object;
@end
// General-purpose implementation of NSFetchedResultsControllerDelegate
@interface KDJFetchedResultsControllerContentUpdater : NSObject <NSFetchedResultsControllerDelegate>
// Table view to be updated
@property (nonatomic, weak) UITableView *tableView;
// Delegate that will configure a cell with updated data when a managed object changes
@property (nonatomic, weak) id<KDJTableViewCellConfigurationDelegate> cellConfigurationDelegate;
@end
#import "KDJFetchedResultsControllerContentUpdater.h"
@implementation KDJFetchedResultsControllerContentUpdater
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
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 {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:@[newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell != nil) {
[self.cellConfigurationDelegate configureCell:cell
withObject:anObject];
}}
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:@[newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
@end
@kristopherjohnson
Copy link
Author

It was annoying to copy the NSFetchedResultsControllerDelegate boilerplate into every table view controller with a fetched results controller, so I created this little reusable class.

To use it, declare your table view to implement the KDJTableViewCellConfigurationDelegate protocol and implement a method like this:

- (void)configureCell:(UITableViewCell *)cell withObject:(NSManagedObject *)managedObject {
    // TODO: configure the cell with data from the managed object
}

(You should probably call this from your -tableView:cellForRowAtIndexPath: method to avoid duplication of cell-configuration code.)

Then, initialize the fetched results controller's delegate like this:

NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest: /* ... */];
self.fetchedResultsController = frc;

KDJFetchedResultsControllerContentUpdater *contentUpdater = [[KDJFetchedResultsControllerContentUpdater alloc] init];
contentUpdater.tableView = self.tableView;
contentUpdater.cellConfigurationDelegate = self;
self.contentUpdater = contentUpdater;
frc.delegate = contentUpdater;

Make sure you retain the content updater yourself, as the fetched results controller won't retain its delegate.

@kristopherjohnson
Copy link
Author

BTW, this snippet of boilerplate from the NSFetchedResultsControllerDelegate documentation is buggy:

case NSFetchedResultsChangeUpdate:
    [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
    break;

See these links for explanation and fixes:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment