To integrate with Core Data, you should only need to replace TLCollapsibleTableViewController's
default TLIndexPathController
with one initialized with an NSFetchRequest
:
- (void)viewDidLoad
{
[super viewDidLoad];
// replace default indexPathController with one configured with an NSFetchRequest
NSManagedObjectContext *context = ...;// Your managed object context
NSFetchRequest *fetch = ...; // Request that fetches Scales sorted by category
self.indexPathController = [[TLIndexPathController alloc] initWithFetchRequest:fetch managedObjectContext:context sectionNameKeyPath:@"categories" identifierKeyPath:nil cacheName:nil];
// perform initial fetch
NSError *error;
[self.indexPathController performFetch:error];
if (error) {
// handle error
}
}
The rest of this answer explains how the Core Data integration works and highlights a cool feature of TLIndexPathControllerDelegate
.
TLCollapsibleTableViewController
uses TLCollapsibleDataModels
as the data model. TLCollapsibleDataModels
are constructed by providing a backing data model containing all possible rows and a set of expanded section names:
TLCollapsibleDataModel *dataModel = [[TLCollapsibleDataModel alloc] initWithBackingDataModel:backingDataModel expandedSectionNames:expandedSectionNames];
The resulting data model only contains the rows to be displayed (though the full data set is accessible through the backingDataModel
property). What TLCollapsibleTableViewController
does is to automatically update the data model when the user expands or collapses a section.
When you enable Core Data integration as described above, the TLIndexPathController
will also automatically update the data model on the initial fetch result and on any fetch result changes. But this data model is the backing model and needs to be converted to TLCollapsibleDataModel
. To make this happen automatically, I added the following implementation of controller:willUpdateDataModel:withDataModel:
, which allows one to arbitrarily modify the new data model before the updates are generated:
- (TLIndexPathDataModel *)controller:(TLIndexPathController *)controller willUpdateDataModel:(TLIndexPathDataModel *)oldDataModel withDataModel:(TLIndexPathDataModel *)updatedDataModel
{
// If `updatedDataModel` is already a `TLCollapsibleDataModel`, we don't need to do anything.
if ([updatedDataModel isKindOfClass:[TLCollapsibleDataModel class]]) {
return nil;
}
// Otherwise, we assume `updatedDataModel` is the backing model - maybe it came from a
// Core Data fetch request or maybe it was provided by custom code - and we need to
// constructe the `TLCollapsibleDataModel`.
NSMutableSet *expandedSectionNames = nil;
// If `oldDataModel` is a `TLCollapsibleDataModel`, we need to preserve the
// expanded state of known sections.
if ([oldDataModel isKindOfClass:[TLCollapsibleDataModel class]]) {
expandedSectionNames = [NSMutableSet setWithSet:((TLCollapsibleDataModel *)oldDataModel).expandedSectionNames];
// Now filter out any section names that are no longer present in `updatedDataModel`
[expandedSectionNames intersectSet:[NSSet setWithArray:updatedDataModel.sectionNames]];
}
// Construct and return the `TLCollapsibleDataModel`
TLCollapsibleDataModel *dataModel = [[TLCollapsibleDataModel alloc] initWithBackingDataModel:updatedDataModel expandedSectionNames:expandedSectionNames];
return dataModel;
}
So if updatedDataModel
isn't a TLCollapsibleDataModel
, it is assumed to be the backing model and is converted to TLCollapsibleDataModel
, preserving the expanded state captured in oldDataModel
. This will not only work for NSFetchRequest
generated updates but you can also set a backing model in your custom code self.indexPathController.items = ...
or self.indexPathController.dataModel = ...
if you have any reason to do so.