Using State Preservation with Core Data
In previous post on state preservation and restoration, I presented the basics. This post deals with something that’s fairly common in data-driven apps.
Core Data is one of my favourite iOS frameworks; I use it in almost all of my apps, anything that requires even a minor object graph. So, let’s see how to save/restore state of table views (it’s the same for collection views) fueled by NSFetchedResultsController.

In Couch to 5k apps, this Runs tab contains UITableViewController subclass. We know from previous post that we should adopt UIDataSourceModelAssociation protocol for its data source:
@interface ProgressViewController : UITableViewController < NSFetchedResultsControllerDelegate, ..., UIDataSourceModelAssociation >
To refresh your memory - this view controller is created in App Delegate, as top level VC for the Runs tab. Thus I only need to set its restorationIdentifier and UIKit will do the rest (no need for the restoration class).
- (id)initWithStyle:(UITableViewStyle)style {
self = [super initWithStyle:style];
if (self) {
self.restorationIdentifier = RTRunStateIdentifierPROGRESS;
...
}
...
}
Saving state
To save the state, the main thing is to choose the modelIdentifier - a string to uniquely identify NSManagedObject (subclass) that corresponds to each cell.
The obvious choice is NSManagedObjectID as it’s universal identifier for the given object. To convert it into string, use [[NSManagedObjectID URIRepresentation] absoluteString] thus for my case the method looks like this:
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx inView:(UIView *)view {
if (!idx || !view) return nil;
NSManagedObject *oneSession = [self.fetchedResultsController objectAtIndexPath:idx];
return [[oneSession.objectID URIRepresentation] absoluteString];
}
And that’s all that’s needed to preserve the (scrolling) state.
Restoring state
This was more fun to figure out and it takes 3 steps:
- get
NSManagedObjectID, usingmanagedObjectIDForURIRepresentation:method ofNSPersistentStoreCoordinator - use that to load actual
NSManagedObjectfrom theNSManagedObjectContextusingexistingObjectWithID:error:method - finally get
NSIndexPaththat we are after, fromNSFetchedResultsControllerusingindexPathForObject:
Here’s the full method:
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view {
if (!identifier || !view) return nil;
// get the objectID first from PSC
NSManagedObjectID *objectID = [self.managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[NSURL URLWithString:identifier]];
if (!objectID) return nil;
// then fetch the object from MOC
NSError *error = nil;
NSManagedObject *oneSession = [self.managedObjectContext existingObjectWithID:objectID error:&error];
if (error) {
NSLog(@"ERROR getting Session object from MOC:\n%@", [error localizedDescription]);
return nil;
}
if (!oneSession) return nil;
// finally get the index path from NSFRC
NSIndexPath *indexPath = [self.fetchedResultsController indexPathForObject:oneSession];
// iOS 6 bug workaround:
// Force a reload when table view is embedded in nav controller or scroll position is not restored.
[self.tableView reloadData];
return indexPath;
}
The workaround at the end is hopefully going away quickly, as we all move to iOS 7 only apps.