Custom Core Data SectionNameKeyPath

14,774

Solution 1

The following should work: Implement the periodYear method (which will be used as "section name key path") in a class extension of your managed object subclass:

@interface Event (AdditionalMethods)
- (NSString *)periodYear;
@end

@implementation Event (AdditionalMethods)
- (NSString *)periodYear {
    return [self.acctPeriod substringToIndex:4];
}
@end

Make sure that acctPeriod is used as the first (or only) sort descriptor for the fetch request:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"acctPeriod" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];

Use periodYear as sectionNameKeyPath for the fetched results controller:

NSFetchedResultsController *_fetchedResultsController = [[NSFetchedResultsController alloc]
                  initWithFetchRequest:fetchRequest 
                   managedObjectContext:self.managedObjectContext 
                     sectionNameKeyPath:@"periodYear"
                              cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;

And finally add the default titleForHeaderInSection method:

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

Alternatively, you can define periodYear as transient attribute of the managed object. It will also not be stored in the database in that case, but can be implemented in a way that the value is calculated on demand and cached.

The DateSectionTitles sample project from the Apple Developer Library demonstrates how this works.

Solution 2

I recommend against using a transient property as the sectionNameKeyPath as it will result in faulting all objects obtained by the fetch request just so the property could be used as the section path.
You better define a persistent property of year and use it as your sectionNameKeyPath.
set you FRC sectionNameKeyPath to year like so:

[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:self.managedObjectContext
                                      sectionNameKeyPath:@"year"
                                               cacheName:nil/*your chioce*/];

to display the section name as a title in the table, implement:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    id<NSFetchedResultsSectionInfo> sec = [self.fetchedResultsController sections][section];
    return [sec name];
}
Share:
14,774
theDVUSone
Author by

theDVUSone

Updated on July 25, 2022

Comments

  • theDVUSone
    theDVUSone almost 2 years

    I am new at core data and am trying to figure out how to create a custom sectionNameKeyPath in my NSFetchedResultsController. I have a managed object with an attribute called acctPeriod. It is a NSString. I want to create sections based on the first 4 characters of this field. The first 4 characters represent the year of the accounting period and doesn't need to be saved.

    I have gone through this site and have seen posts about transient attributes but I can't seem to get them to work. Basically I want this and then assign periodYear for my sectionNameKeyPath.

    @dynamic periodYear;
    
    -(NSString *)periodYear
    {
        return [self.acctPeriod substringToIndex:4];
    }
    

    Any help would be appreciated.

    **UPDATE: Using Martin R. answer, I was able to get it to work as expected.

    - (NSFetchedResultsController *)fetchedResultsController
    {
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }
    
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Billing" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    
    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];
    
    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"acctPeriod" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];
    
    //Predicate
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"clients = %@", self.client];
    NSLog(@"%@",pred);
    
    //[fetchRequest setResultType:NSDictionaryResultType];
    //[fetchRequest setReturnsDistinctResults:YES];
    
    [fetchRequest setPredicate:pred];
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"periodYear" cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
    
    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _fetchedResultsController;  
    }
    
  • theDVUSone
    theDVUSone about 11 years
    OK, that worked great. I tried this before but the problem is that I'm trying only to load 1 of my entities, acctPeriod, and sort it by periodYear. So I was using [fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:[entityProperties objectForKey:@"acctPeriod"],nil]]; which doesn't seem to work. I tried to put periodYear as entityProperty but I know that it's not. I get this error: 'Invalid keypath periodYear passed to setPropertiesToFetch:' Any thoughts?
  • Martin R
    Martin R about 11 years
    @theDVUSone: If you use propertiesToFetch then you probably have to define periodYear as transient attribute and add it to the propertiesToFetch. I did not try it, but that is what I assume.
  • InitJason
    InitJason about 10 years
    I was making the mistake of declaring a @property that implemented a getter. This solution more or less pointed out that it should just be a method declaration.