I have three sections in my tableView:
Today
Upcoming
Past
When the app launches, the view controller that's launched has an NSFetchedResultsController. If I delete the app and relaunch it in simulator for the first time, it runs great; however, when the date changes, meaning the next day of after, the app gives the error below:
2014-06-29 19:48:35.326 App[37398:4803] CoreData: error: (NSFetchedResultsController) The fetched object at index 4 has an out of order section name ' Upcoming. Objects must be sorted by section name'
2014-06-29 19:48:35.328 App[37398:4803] Unresolved error Error Domain=NSCocoaErrorDomain Code=134060 "The operation couldn’t be completed. (Cocoa error 134060.)" UserInfo=0xf990fc0 {reason=The fetched object at index 4 has an out of order section name ' Upcoming. Objects must be sorted by section name'}, {
reason = "The fetched object at index 4 has an out of order section name ' Upcoming. Objects must be sorted by section name'";
}
The index varies.
NSFetchedResultsConroller setup:
- (NSFetchedResultsController *)fetchedResultsController
{
if(_fetchedResultsController!=nil)
{
return _fetchedResultsController;
}
if (!self.objectManager)
{
self.objectManager = [self getObjectManager];
}
self.managedObjectContext = self.objectManager.managedObjectStore.mainQueueManagedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Meeting"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
sortKey
is not transient. I'm not sure if it should be or not.
NSSortDescriptor *firstSort = [[NSSortDescriptor alloc] initWithKey:@"sortKey"
ascending:YES];
NSSortDescriptor *secondSort = [[NSSortDescriptor alloc] initWithKey:@"modifiedDate"
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:firstSort, secondSort, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:@"sectionIdentifier"
cacheName:nil];
self.fetchedResultsController.delegate = self;
return self.fetchedResultsController;
}
I followed Apple's sample code and created a transient property sectionIdentifier
and sortKey
to always display sections in the following order:
1. Today
2. Upcoming
3. Past
If objects are not available for any of the sections, that section does not appear. I'm using modifiedDate
to determine what section
the object belongs to.
Most of the questions on SO claim that the first sort should be sectionIdentifier
, but that's a transient property. Plus, Apple's example doesn't use sectionIdentifier
as first sort. Any suggestions?
Edit
Here is my NSManagedObject file .m:
@dynamic primitiveSectionIdentifier, primitiveSortKey, primitiveModifiedDate, primitiveStartDate;
#pragma mark - Transient properties
- (NSString *)sectionIdentifier
{
// Create and cache the section identifier on demand.
[self willAccessValueForKey:@"sectionIdentifier"];
NSString *tmp = [self primitiveSectionIdentifier];
[self didAccessValueForKey:@"sectionIdentifier"];
[self willAccessValueForKey:@"sortKey"];
NSNumber *tempSort = [self primitiveSortKey];
[self didAccessValueForKey:@"sortKey"];
if (!tmp)
{
NSDate *dateToCompare = [self getcurrentTime:[self startDate]];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDate* now = [self getcurrentTime:[NSDate date]];
NSDateFormatter *format = [[NSDateFormatter alloc] init];
format.dateFormat = @"dd-MM-yyyy";
format.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
NSString *stringDate = [format stringFromDate:now];
NSDate *todaysDate = [format dateFromString:stringDate];
NSInteger differenceInDays =
[calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:dateToCompare] -
[calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:todaysDate];
NSLog(@"differenceInDays %i", differenceInDays);
NSString *sectionString;
NSNumber *sortNumber;
if (differenceInDays == 0)
{
sectionString = kSectionIDToday;
sortNumber = [NSNumber numberWithInt:0];
}
else if (differenceInDays < 0)
{
sectionString = kSectionIDPast;
sortNumber = [NSNumber numberWithInt:2];
}
else if (differenceInDays > 0)
{
sectionString = kSectionIDUpcoming;
sortNumber = [NSNumber numberWithInt:1];
}
tmp = sectionString;
tempSort = sortNumber;
[self setPrimitiveSectionIdentifier:tmp];
[self setPrimitiveSortKey:tempSort];
}
return tmp;
}
-(NSDate *)getcurrentTime:(NSDate*)date
{
NSDate *sourceDate = date;
NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];
NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];
NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;
NSDate* currentDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate];
return currentDate;
}
#pragma mark - Time stamp setter
- (void)setStartDate:(NSDate *)newDate
{
// If the time stamp changes, the section identifier become invalid.
[self willChangeValueForKey:@"startDate"];
[self setPrimitiveStartDate:newDate];
[self didChangeValueForKey:@"startDate"];
[self setPrimitiveSectionIdentifier:nil];
}
- (void)setSortKey:(NSNumber *)sortKey
{
// If the time stamp changes, the section identifier become invalid.
[self willChangeValueForKey:@"sortKey"];
[self setPrimitiveSortKey:sortKey];
[self didChangeValueForKey:@"sortKey"];
[self setPrimitiveSortKey:nil];
}
#pragma mark - Key path dependencies
+ (NSSet *)keyPathsForValuesAffectingSectionIdentifier
{
// If the value of timeStamp changes, the section identifier may change as well.
return [NSSet setWithObject:@"sortKey"];
}
+ (NSSet *)keyPathsForValuesAffectingSortKey
{
// If the value of timeStamp changes, the section identifier may change as well.
return [NSSet setWithObject:@"modifiedDate"];
}
Edit 2
I delete the app in the simulator and relaunch it. The first time I see the values for sortKey
are saved in Core Data (I verified it by looking into .sqllite file). Next day when the device date has changed, I relaunch the app, the error appears. When I look in CoreData, sortKey
values have not changed. I believe if I can get the values for sortKey
to refresh each time the app is launched, perhaps the error might go away.
Edit 3
I removed (!tmp) from sectionIdentifier
to see that helps, but no avail. No data changes in Core Data.
- (NSString *)sectionIdentifier
{
[self willAccessValueForKey:@"sectionIdentifier"];
NSString *tmp = [self primitiveSectionIdentifier];
[self didAccessValueForKey:@"sectionIdentifier"];
[self willAccessValueForKey:@"sortKey"];
NSNumber *tempSort = [self primitiveSortKey];
[self didAccessValueForKey:@"sortKey"];
NSDate *dateToCompare = [self getcurrentTime:[self startDate]];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDate* now = [self getcurrentTime:[NSDate date]];
NSDateFormatter *format = [[NSDateFormatter alloc] init];
format.dateFormat = @"dd-MM-yyyy";
format.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
NSString *stringDate = [format stringFromDate:now];
NSDate *todaysDate = [format dateFromString:stringDate];
NSInteger differenceInDays =
[calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:dateToCompare] -
[calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:todaysDate];
NSString *sectionString;
NSInteger sortNumber = 0;
if (differenceInDays == 0)
{
sectionString = kSectionIDToday;
sortNumber = 0;
}
else if (differenceInDays < 0)
{
sectionString = kSectionIDPast;
sortNumber = 2;
}
else if (differenceInDays > 0)
{
sectionString = kSectionIDUpcoming;
sortNumber = 1;
}
if (sortNumber != [tempSort integerValue])
{
tmp = sectionString;
tempSort = [NSNumber numberWithInt:sortNumber];
[self setPrimitiveSectionIdentifier:tmp];
[self setPrimitiveSortKey:tempSort];
}
return tmp;
}
sortKey
? When you get a failure, log the fetch result. Show the code forsectionIdentifier
. – Palazzo