Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Core Data SectionNameKeyPath

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;  
}
like image 408
theDVUSone Avatar asked Apr 08 '13 16:04

theDVUSone


2 Answers

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.

like image 73
Martin R Avatar answered Nov 12 '22 04:11

Martin R


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];
}
like image 20
Dan Shelly Avatar answered Nov 12 '22 05:11

Dan Shelly