Skip to content

Instantly share code, notes, and snippets.

@benvium
Created May 26, 2015 14:11
Show Gist options
  • Select an option

  • Save benvium/5405c4ef14c0b2b394f0 to your computer and use it in GitHub Desktop.

Select an option

Save benvium/5405c4ef14c0b2b394f0 to your computer and use it in GitHub Desktop.
Implements the boilerplate required to update a UITableView based on results from an NSFetchedResultsController
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@import CoreData;
typedef NS_ENUM(NSUInteger, FetchResultControllerMode) {
FetchResultsTableViewUpdaterModeMultipleSection,
FetchResultsTableViewUpdaterModeSingleFixedSection
};
/**
* Implements the boilerplate required to update a UITableView based on results from an NSFetchedResultsController
*
* Can be used to control all sections in a UITableView
* - use mode == FetchResultControllerModeMultipleSection
* - fixedSectionIndex is then ignored.
*
* OR multiple instances of NSFetchedResultsController can be used to each control a single section.
*
* - use mode == FetchResultControllerModeSingleFixedSection
* - fixedSectionIndex is the index of the section this NSFetchedResultsController is using
*
*/
@interface FetchResultsTableViewUpdater : NSObject <NSFetchedResultsControllerDelegate>
@property(nonatomic, weak) UITableView *tableView;
@property(nonatomic, weak) NSFetchedResultsController *fetchedResultsController;
// Used to pass on calls if required TODO: only used in the didChangeObject call so far
@property(nonatomic, weak) id <NSFetchedResultsControllerDelegate> delegate;
/**
* fixedSectionIndex = only used if mode is FetchResultControllerModeSingleFixedSection, in which case the delegates will always pass fixedSectionIndex for the section.
*/
- (instancetype)initWithTableView:(UITableView *)tableView
fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController
mode:(FetchResultControllerMode)mode
fixedSectionIndex:(NSInteger)fixedSectionIndex;
@end
#import "FetchResultsTableViewUpdater.h"
@interface FetchResultsTableViewUpdater ()
@property(nonatomic) FetchResultControllerMode mode;
@property(nonatomic) NSInteger fixedSectionIndex;
@end
@implementation FetchResultsTableViewUpdater {
}
- (instancetype)initWithTableView:(UITableView *)tableView
fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController
mode:(FetchResultControllerMode)mode
fixedSectionIndex:(NSInteger)fixedSectionIndex {
self = [super init];
if (self) {
self.tableView = tableView;
self.fetchedResultsController = fetchedResultsController;
self.fetchedResultsController.delegate = self;
self.mode = mode;
self.fixedSectionIndex = fixedSectionIndex;
}
return self;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSLog(@"Beginning updates");
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
if (self.mode == FetchResultsTableViewUpdaterModeSingleFixedSection) {
if (indexPath) {
indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:self.fixedSectionIndex];
}
if (newIndexPath) {
newIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:self.fixedSectionIndex];
}
}
UITableView *tableView = self.tableView;
switch (type) {
case NSFetchedResultsChangeInsert: {
NSLog(@"Inserting at path %@", newIndexPath);
[tableView insertRowsAtIndexPaths:@[newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeDelete: {
NSLog(@"Deleting at path %ld", (long) indexPath.row);
[tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeUpdate:
NSLog(@"Updating at path %@", indexPath);
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
[tableView reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
break;
case NSFetchedResultsChangeMove:
NSLog(@"Moving from path %@ to %@", indexPath, newIndexPath);
[tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:@[newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
// Pass on to self.delegate
if ([((NSObject *) self.delegate) respondsToSelector:@selector(controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:)]) {
[self.delegate controller:controller
didChangeObject:anObject
atIndexPath:indexPath
forChangeType:type
newIndexPath:newIndexPath];
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
UITableView *tableView = self.tableView;
if (self.mode == FetchResultsTableViewUpdaterModeMultipleSection) {
if (type == NSFetchedResultsChangeInsert) {
NSLog(@"Inserting section at %lu", (unsigned long) sectionIndex);
NSIndexSet *insertedSection = [NSIndexSet indexSetWithIndex:sectionIndex];
[tableView insertSections:insertedSection withRowAnimation:UITableViewRowAnimationFade];
}
else if (type == NSFetchedResultsChangeDelete) {
NSLog(@"Deleting section at %lu", (unsigned long) sectionIndex);
NSIndexSet *deletedSection = [NSIndexSet indexSetWithIndex:sectionIndex];
[tableView deleteSections:deletedSection withRowAnimation:UITableViewRowAnimationFade];
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSLog(@"Ending updates");
NSLog(@"Fetched %ld Items After Change", (unsigned long) self.fetchedResultsController.fetchedObjects.count);
@try {
[self.tableView endUpdates];
}
@catch (NSException *ex) {
NSLog(@"RBQFecthResultsTVC caught exception updating table view: %@. Falling back to reload.", ex);
// TODO: no 'reset' method on real NSFetchedResults controller
// [self.fetchedResultsController reset];
[self.tableView reloadData];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment