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

两盒软妹~` 提交于 2019-12-08 05:22:55

问题


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;
}

回答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.




回答2:


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.



来源:https://stackoverflow.com/questions/24482449/nsfetchedresultscontroller-the-fetched-object-at-index-x-has-an-out-of-order-se

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!