Section Headers for NSFetchedResultsController

25 May 2014

Recently, I wanted to add section headers to an NSFetchedResultsController, so that I could have sections in the corresponding UICollectionView. The Apple Documentation is clear that you add a value to the sectionNameKeyPath method when you are creating the controller. Setting the value to nil will return a fetched results controller with only 1 section. Providing a value for the sectionNameKeyPath will create one section per unique value of the key path.

I wanted to sort by date so that I would have one section for each day. My first attempt was to simply add the date property of my object to the sectoinNameKeyPath.

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                                                managedObjectContext:[self managedObjectContext]
                                                                                                  sectionNameKeyPath:@"sessionDate"
                                                                                                           cacheName:@"Sessions"];

This allowed me to get values for the number of sections and titles for the sections.

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return [[[self fetchedResultsController] sections] count];
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    MTNFolderHeader *header = nil;
    if (kind == UICollectionElementKindSectionHeader) {
        header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"Header" forIndexPath:indexPath];
         id <NSFetchedResultsSectionInfo> sectionInfo = [[[self fetchedResultsController] sections] objectAtIndex:[indexPath section]];
            [[header titleLabel] setText:[sectionInfo name]];
    }
    return header;
}

Some of this code is specific to my application, but I can get the count of the sections array and I can get the [sectionInfo name] for the title of the header. If you are using a UITableView the header viewForHeaderInSection code would be simpler than for the collection view.

However, I store records by timestamp. So, when I first implemented my fetch I was getting one section per record because the timestamps are unique (they include data beyond the day).

In order to create the grouping the way I want them. I will need to add a property to my objects. A few important notes from the documentation. When creating a custom grouping, the custom grouping must either be included as the primary sort key or else it must not modify the sort order. This means that if I sort by date as my primary sort, I cannot try to group the records by the firstName property. Also, the grouping key can either be an actual property of the object, a transient property or a method. In my case, since I just wanted to display the dates in a more human friendly format, I decided just to create a method on my objects.

Session.h

- (NSString *) sectionTitle;

Session.m

- (NSString*)sectionTitle {
    NSString *dateFormatTemplate = [NSDateFormatter dateFormatFromTemplate:@"MMMM d" options:0 locale:[NSLocale currentLocale]];
    [[self formatter] setDateFormat:dateFormatTemplate];
    return [[self formatter] stringFromDate:[self date]];

}

Now I modify my fetched results controller to use the new method.

    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                                                managedObjectContext:[appD managedObjectContext]
                                                                                                  sectionNameKeyPath:@"sectionTitle"
                                                                                                           cacheName:@"Sessions"];
                                                                                                          

And now I am getting my sessions grouped by day!