Question

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;  
}
Was it helpful?

Solution

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.

OTHER TIPS

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];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top