NSFetchedResultsController: The fetched object at index x has an out of order section name
Asked Answered
C

2

1

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:

https://developer.apple.com/library/ios/samplecode/DateSectionTitles/Listings/DateSectionTitles_APLEvent_m.html

   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;
}
Catercornered answered 30/6, 2014 at 3:7 Comment(2)
What is sortKey? When you get a failure, log the fetch result. Show the code for sectionIdentifier.Palazzo
Thanks. I added an Edit. How do I get the fetch results? Log what out? It seems like the failure occurs when the system time/Date changes. If I change the date on the system to the next day and relaunch the app in the simulator, failure occurs. Prior to changing the date I'll have several objects in my tableView. After changing the date, the failure occurs and I get this error. However, if I change the date back to what it was before, no failures and the objects appear in the tableView. BTW, the app doesn't crash, it continues to run, but the tableview is blank.Catercornered
F
1

I am pretty sure your sorting code can be greatly simplified. But maybe that is a different question. Anyway, it is not clear, if startDate is set and there are too many variables which make the algorithm very convoluted. Could you try to refactor just using a date and a section identifier? The section identifier should sort in the correct way - you can add the logic to display some customised string elsewhere.

Here is something you should try. Eliminate the if (!tmp) check - I had some luck with that fixing this sample code for my purposes.

Finally, it is not clear how you update the FRC when the date changes. Make sure the data that you expect to change is updated as intended.

Fadden answered 30/6, 2014 at 22:9 Comment(10)
Thank you! So, before without the sortKey, I had the FRC working with startDate and sectionIdentifier. My reason to introduce a sortKey was to get Today to appear before Upcoming. With startDate and sectionIdentifier, when I turn ascending on, Upcoming appears first, followed with Today and Past.Catercornered
I update the tableView when FRC delegates controllerDidChangeContent and others. Would you to see the code?Catercornered
The critical part is when you change the data, not what happens once the data is changed. I suppose a date change (time passing) or restarting the app is such a case. You should maybe invalidate the FRC (set it to nil) to let it recompute the sections. -- You can keep the sortKey attribute (for sorting) but reduce the other variables.Fadden
I removed (!tmp) and did a third edit in the question. Is this what you had suggested? Thanks.Catercornered
Please reduce the code in your question and instead post the code that you call when you update the data based on a new time.Fadden
If I'm understanding you correctly, Edit 3 is what I was hoping would to update the data in db. When I look in .sqlite file, the data is inserted the first time when the data if fetched from server. After that the data is never updated.Catercornered
Obviously, your update code lives in the entity. Something in your app has to tell it to update itself (maybe a timer?).Fadden
I did a sample project that duplicates this error: github.com/KausiAhmed/FRCCatercornered
So I tried another approach with Three FRC. I got further than one FRC: #24556623Catercornered
Still, you should accept the answers above and here if it helped you.Fadden
P
1

Your main issue seems to be triggering an update process to run and modify your data store contents.

When you update the sectionIdentifier and sortKey you can store the date they are calculated against and compare the date when requested to check it's still valid. This gives you an easy way to check if any updates are required when you receive a trigger to tell you that something has changed and you need to verify.

For the triggers, you need to consider the app coming to the foreground, significant time changes, and just the day changing while the app is open. So you can trigger from the app delegate (or observing activation), and you can observe UIApplicationSignificantTimeChangeNotification and you can configure a timer (when the app is activated) which counts down to the end of the day (invalidate this when the app goes to background).

Now you have triggers and action to take as a result. But, your view controller won't always exist when a trigger is received, so consider using a data update controller to manage this process.

Palazzo answered 30/6, 2014 at 7:37 Comment(7)
The purpose for sortKey is to make sure that the sections always appear in the following order: Today, upcoming, and past. If I take out the sortKey then Upcoming or Past appear first, depending upon if sorting for ascending is on. I need to keep the order of section as mentioned earlier.Catercornered
So do you still suggest that I get rid of the sortKey? How will I get sections in that order?Catercornered
Ok, then keep sort key and save the date then refresh when the date shows as old.Palazzo
I'm sorry. I'm confused. When you say "save the date" meaning that I do a fetch get all the objects, compare them with current date, and based on the results, I provide new values for sortKey; save MOC; refresh FRC? Where do I do all of this in? In the viewController that uses FRC? Can you please adjust the answer.Catercornered
Thank you! I'm going to do a sample project based on your suggestion and my understanding. I'll push it up and post the link here.Catercornered
Okay...so I did a sample project and simplified the code a lot. I'm still getting the The fetched object at index error. Sample Project: github.com/KausiAhmed/FRCCatercornered
So I tried another approach with Three FRC. I got further than one FRC: #24556623Catercornered
F
1

I am pretty sure your sorting code can be greatly simplified. But maybe that is a different question. Anyway, it is not clear, if startDate is set and there are too many variables which make the algorithm very convoluted. Could you try to refactor just using a date and a section identifier? The section identifier should sort in the correct way - you can add the logic to display some customised string elsewhere.

Here is something you should try. Eliminate the if (!tmp) check - I had some luck with that fixing this sample code for my purposes.

Finally, it is not clear how you update the FRC when the date changes. Make sure the data that you expect to change is updated as intended.

Fadden answered 30/6, 2014 at 22:9 Comment(10)
Thank you! So, before without the sortKey, I had the FRC working with startDate and sectionIdentifier. My reason to introduce a sortKey was to get Today to appear before Upcoming. With startDate and sectionIdentifier, when I turn ascending on, Upcoming appears first, followed with Today and Past.Catercornered
I update the tableView when FRC delegates controllerDidChangeContent and others. Would you to see the code?Catercornered
The critical part is when you change the data, not what happens once the data is changed. I suppose a date change (time passing) or restarting the app is such a case. You should maybe invalidate the FRC (set it to nil) to let it recompute the sections. -- You can keep the sortKey attribute (for sorting) but reduce the other variables.Fadden
I removed (!tmp) and did a third edit in the question. Is this what you had suggested? Thanks.Catercornered
Please reduce the code in your question and instead post the code that you call when you update the data based on a new time.Fadden
If I'm understanding you correctly, Edit 3 is what I was hoping would to update the data in db. When I look in .sqlite file, the data is inserted the first time when the data if fetched from server. After that the data is never updated.Catercornered
Obviously, your update code lives in the entity. Something in your app has to tell it to update itself (maybe a timer?).Fadden
I did a sample project that duplicates this error: github.com/KausiAhmed/FRCCatercornered
So I tried another approach with Three FRC. I got further than one FRC: #24556623Catercornered
Still, you should accept the answers above and here if it helped you.Fadden

© 2022 - 2024 — McMap. All rights reserved.