How to implement Table Headers with NSFetchedResultsController in Core Data

iOS Simulator Screen Shot 28 Aug 2015 15.48.03To use sections in a table view that’s powered by Core Data and a fetched results controller, we need to specify a property called sectionNameKeyPath. It’s the key to making those header titles show up. Reading Apple’s ever so colourful documentation however doesn’t give us a clue what this means in human terms.

Here’s how to implement those with a simple example.

Starting out from the Master/Detail template in Xcode, create a new project and choose Core Data. This will setup the fetched results controller along with the rest of the Core Data stack. We’ll also get one handy Event entity with a single attribute: an NSDate called timeStamp. Run the app, click the plus button at the top and we’ll see a new date is inserted into the table.

But the table has no sections. Let’s assume we’d like to create a new section for every minute that has been logged in the database. So 18:07 would have a section, 18:08, and whatever else is available.

 

Creating Managed Object Subclasses

First we’ll create a managed object subclass from our model so that we can write some code for this. Choose the .xcdatamodeld file in Xcode and select Editor – Create NSManagedObject Subclass. Stash the two resulting files in your project folder.

In Even.h, add another property. It won’t be saved, it’ll be calculated on the fly. We already have the timeStamp property for the date object, so let’s call the new property minuteSection. Here’s the Even.h file:

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


@interface Event : NSManagedObject

@property (nonatomic, retain) NSDate * timeStamp;
@property (nonatomic, retain) NSString *minuteSection;

@end

In the Event.m file we’ll create a custom initialiser, which will turn our timeStamp into time string. Since this is a Core Data property we’ll have to mark it as dynamic, much like all the other Core Data properties in managed object subclasses:

#import "Event.h"


@implementation Event

@dynamic timeStamp;
@dynamic minuteSection;

- (NSString *)minuteSection {
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
    formatter.timeStyle = NSDateFormatterShortStyle;
    NSString *sectionTitle = [formatter stringFromDate:self.timeStamp];
    
    return sectionTitle;
}

@end

Here we use an NSDateFormatter that turns the long date object into something like 18:07. Every event in our database logged at 18:07 for example will get its own section with that title.

 

Tweaking the Fetched Results Controller

Next, let’s turn our attention to the NSFetchedResultsController. In the Xcode template it’s in MasterViewController.m. There’s a custom initialiser with a method that by default looks like this:

// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
                                                         initWithFetchRequest:fetchRequest
                                                         managedObjectContext:self.managedObjectContext
                                                         sectionNameKeyPath:nil
                                                         cacheName:@"Master"];

Take a look at the sectionNameKeyPath property here: it’s set to nil, meaning we’re not displaying any sections. To tell the controller which property we want to use as section criteria, all we have to do is provide the string of the property we’d like to use. In our case, that’s minuteSection. Quick modification:

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
                                                         initWithFetchRequest:fetchRequest
                                                         managedObjectContext:self.managedObjectContext
                                                         sectionNameKeyPath:@"minuteSection"
                                                         cacheName:nil];

I’ve also taken the liberty to remove the cache and set it to nil, we don’t really need it here.

 

Showing the Section Titles

Right now the app will run as usual, but we still won’t see any new sections. Even though they are displayed, we don’t see them separated from the cells. What we need to do is add a method to our table view delegate and make those sections show up with the right titles.

In MasterViewController.m, hunt for a pragma mark called Table View. You’ll find methods to show how many rows and sections there are, both of which are being read from the fetched results controller. Examine the code to spot some similarities to what we’re doing next.

Add a method called titleForHeaderInSection, it comes up as part of the automatic code completion as soon as you type “- table” (it’s a long list). Here’s what we need to add to it for those section headers to show up:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    
    id  sectionInfo = [self.fetchedResultsController.sections objectAtIndex:section];
    return [sectionInfo name];
}

It’s only two lines of code, but they’re rather long. Here we grab the controller’s current section and call it’s name method, which returns the string we’re looking for (i.e. 18:07, the hour and minute we’d like to use).

Run the app and add a few values to it. On ever new minute, a new section is created. When you remove values, empty sections are automatically removed as well.

 

Further Reading

 

Demo Project

I’ve got a working demo project on GitHub – feel free to check it out:

 

 

iOS Simulator Screen Shot 28 Aug 2015 15.48.03





Leave a Reply