Fetch from Core Data sorted by formatted date

2019-08-27 02:30发布

问题:

I am having trouble fetching results from Core Data that are sorted by date.

I have a DB table that contains football matches. Each match has a homeTeam, awayTeam and kickoffTime. The kickoffTime is an NSDate that stores the date and time the match will start.

I want to display the results of a query in a TableView divided into sections by the kickoff date. With the date as the section heading.

This is a little more complex than it might first appear. Due to differing time zones a match starting on one date in one part of the world is actually starting on a different date in another part of the world. So I can't simply ignore the times and store the kickoff dates in another column.

What I'm trying to do create a custom accessor that returns a formatted date, in whatever time zone the user is in, and then use this to sort and section the results. Here's my code in Match.h:

@dynamic kickoffTime;
@dynamic formattedKickoffTime;
@dynamic dateFormatter;

- (NSString *)formattedKickoffTime
{
    [self willAccessValueForKey:@"kickoffTime"];

    // Set the date formatter to the format we want to display the date
    [dateFormatter setDateFormat:@"ccc, d MMM"];

    // Format the date
    NSString *myFormattedKickoffTime = [dateFormatter stringFromDate:[self kickoffTime]];

    [self didAccessValueForKey:@"kickoffTime"];

    // return the formatted date
    return myFormattedKickoffTime;
}

- (NSDateFormatter *)dateFormatter 
{   
    if (dateFormatter == nil) 
    {
        dateFormatter = [[NSDateFormatter alloc] init];
    }
    return dateFormatter;
}

@end

However when I try to fetch and sort the data like so:

NSSortDescriptor *kickoffDescriptor = [[NSSortDescriptor alloc] initWithKey:@"formattedKickoffTime" ascending:YES];
...
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:appDelegate.managedObjectContext sectionNameKeyPath:@"formattedKickoffTime" cacheName:nil];

I get the following error message:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath formattedKickoffTime not found in entity <NSSQLEntity Match id=1>'

Would someone offer some advice please?

回答1:

So I've found a way to do this. First of all I keep all the dates and calculations in the model in GMT. I add a custom accessor (kickoffDate) to my Match entity that gets the kickoffTime, sets it's time to 00:00:00 and returns this. One thing I had to do, that wasn't obvious, was set the timeZone of the date returned by the custom accessor to GMT as otherwise this was being set to the user's system timeZone.

As all the dates returned by the model are in GMT everything just seems to work as it should no matter what timeZone I set in the iPhone's Settings. I guess Core Data is smart enough to do the timeZone adjustments before it puts the matches in the sections.

I do have one question though, is [NSTimeZone timeZoneForSecondsFromGMT:0] the best way to get a 0 timeZone, by this I mean a timeZone that isn't affected by daylight saving time?

Oh and do I have to call [self willAccessValueForKey:@"kickoffTime"]; and [self didAccessValueForKey:@"kickoffTime"];? I don't really understand what these are doing!

Here's the code in Match.h:

...
@dynamic homeTeam;
@dynamic awayTeam;
@dynamic kickoffTime;

- (NSDate *)kickoffDate
{
    [self willAccessValueForKey:@"kickoffTime"];
    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDateComponents *components = [cal components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:[self primitiveValueForKey:@"kickoffTime"]];
    [components setHour:0];
    [components setMinute:0];
    [components setSecond:0];
    [components setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

    [self didAccessValueForKey:@"kickoffTime"];
    return [cal dateFromComponents:components];
}

And then my fetch code includes the following a sort on kickoffTime and the sectionNameKeyPath is set to kickoffDate:

NSSortDescriptor *kickoffDescriptor = [[NSSortDescriptor alloc] initWithKey:@"kickoffTime" ascending:YES];
...
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:appDelegate.managedObjectContext sectionNameKeyPath:@"kickoffDate" cacheName:nil];