22

I know I'm not the first to ask this question but I'm really stumped..

Basically I have a screen with two buttons. Each button loads data into a tableview below based on a date. On the first load of the first tableview (the left button is selected by default) everything displays fine. If I click on the right button and I get a blank tableview, and I get the error

The fetched object at index x has an out of order section name 'xxxxxx. Objects must be sorted by section name.

Switching back to the left table view, the data is gone. Both tableviews are empty.

Each tableview has 2 sections depending on the start time of the item. If I eliminate the sections the data displays fine. Unfortunately I need them.. the data is sorted into the two sections like so:

@interface NSString(agendaSessionKeyPath)
@property (nonatomic, readonly) NSString *sessionSection;
@end

@implementation NSString(agendaSessionKeyPath)

- (NSString *)sessionSection
{
    int timeValue = [[self stringByReplacingOccurrencesOfString:@":" withString:@""] intValue]; //turns 11:00 to 1100
    if (timeValue < 1200)
        return @"Morning";
     else
        return @"Afternoon";
}

Fetch request

- (void)viewDidLoad
{
     //other viewDidLoad stuff
    [self fetchSessions];
}

method which sorts the data from the left and right button based on date:

- (void)fetchSessions
{
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];

    NSDate* date = nil;
    if (selected == 0) //left button is selected
    {
        date = [dateFormatter dateFromString:@"2012-09-26"];
    }
    else if (selected == 1) //right button is selected
    {
        date = [dateFormatter dateFromString:@"2012-09-27"];
    }

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"date == %@", date];
    [self.fetchedResultsController.fetchRequest setPredicate:predicate];

    NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }
}

fetched results controller

- (NSFetchedResultsController *)fetchedResultsController {
    self.managedObjectContext = [[MATCDatabaseController sharedDatabaseController] managedObjectContext];
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Session"];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc]
                              initWithKey:@"title" ascending:YES];
    NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:@"timeValue" ascending:YES];
    [fetchRequest setSortDescriptors:@[timeSort, sort]];
    [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"startTime.sessionSection"
                                                   cacheName:nil];

    self.fetchedResultsController = theFetchedResultsController;
    [self.fetchedResultsController setDelegate:self];

    return _fetchedResultsController;

}

any help is appreciated!

sixstatesaway
  • 1,106
  • 1
  • 12
  • 25
  • 1
    While you provided a fair amount of code, it is not obvious at all what you expect to happen, and what problem you are actually encountering (and when the error in the title occurs). Please provide a bit more context. Not many are going to read through code without a better description of the problem. – Jody Hagins Aug 27 '12 at 23:27
  • thanks for the feedback. I will edit the question with more information. – sixstatesaway Aug 28 '12 at 05:51

2 Answers2

27

OK, I did take a quick peek.

You initialize the FRC with:

[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:self.managedObjectContext
                                      sectionNameKeyPath:@"startTime.sessionSection"
                                               cacheName:nil];

which tells it that your section titles are to be obtained via the key path startTime.sessionSection.

Now, the first sort descriptor of a fetch request that is given to a FRC will be used to sort the sections. The sort descriptor you are providing first is for timeValue which does not seem right.

Your first sort descriptor should specify a sort for your section titles. Change that and you may be good to go.

EDIT

Thanks guys for the info. I'm still a bit lost though. Did you mean that I should add a sort descriptor on startTime.sessionSection before assigning it to the sectionNameKeyPath? I tried, but still no luck. timeValue and startTime.sessionSection are related. Could that be it? – pigeonfactory

You have to make sure that the very first sort descriptor will properly sort your data based on the section. In your case, times are being converted into words. Your initial sort descriptor is for times, and when the data is sorted based on time, the sections are not sorted properly, which is causing your error.

The very first sort descriptor must satisfy the section data. So, initially, I would try...

[fetchRequest setSortDescriptors:@[
    [NSSortDescriptor sortDescriptorWithKey:@"startTime.sessionSection"
                                  ascending:NO],
    [NSSortDescriptor sortDescriptorWithKey:@"timeValue"
                                  ascending:YES],
    [NSSortDescriptor sortDescriptorWithKey:@"title"
                                  ascending:YES] ];

Note, if you have lots and lots of data, you may find that your section mechanism gets slow. If that happens, you may want to add this section data to your database.

Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
  • 3
    Yup. I've had the same problem before, and this is the solution. Sort first by the same key path as your section index, then by whatever you want rows within a section sorted by. – rickster Aug 28 '12 at 03:23
  • Thanks guys for the info. I'm still a bit lost though. Did you mean that I should add a sort descriptor on `startTime.sessionSection` before assigning it to the `sectionNameKeyPath`? I tried, but still no luck. `timeValue` and `startTime.sessionSection` are related. Could that be it? – sixstatesaway Aug 28 '12 at 06:46
  • Saying "I tried but still no luck" does not say what you tried, how you did it, and what result you received. – Jody Hagins Aug 28 '12 at 12:31
  • Hi Jody. You're right. Sorry. What I did was create a sort descriptor in my `fetchedResultsController` like this: `NSSortDescriptor *sectionSort = [NSSortDescriptor sortDescriptorWithKey:@"startTime.sessionSection" ascending:YES];` Even with the extra sort descriptor, I saw no change. The table view was always empty on the second load. I actually solved it, it was totally unrelated to what I thought. [This question](http://stackoverflow.com/questions/10481860/transient-sectionnamekeypath-nssortdescriptor-nsfetchedresultscontroller) helped me. Thanks anyway for the help. – sixstatesaway Aug 28 '12 at 16:54
  • Doh. I was trying to guess what your problem was, and zeroed on the initial sort descriptor because that is a common mistake. I totally overlooked the sessionSection. Fare well. – Jody Hagins Aug 28 '12 at 17:29
0

I also have got a similar issue, perhaps someone find it useful.

I fetched financial transactions from DB and sectioned them by Month.

DB has property trDate - which is a transaction date. But trMonth is a transient property like:

- (NSString*)trMonth {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"LLLL"];
[dateFormatter setLocale:[NSLocale currentLocale]];

return [dateFormatter stringFromDate:trDate];
}

It was used in FetchResultsController like this:

...
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:@"trDate" ascending:YES];

[fetchRequest setSortDescriptors:@[sortDate]];

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"trMonth" cacheName:nil];
...

Which worked well in production, but once app started to crash, and after discovering the root issue I figured out that there two transaction for (for instance) on July: July 2014 and July 2015. That was the crash point as trDate sorted well July 2014 < July 2015, but my method considered them as the same month. The solution was use as section names not only the month name but year also:

- (NSString*)trMonthYear {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"LLLL yyyy"]; // added year also
[dateFormatter setLocale:[NSLocale currentLocale]];

return [dateFormatter stringFromDate:trDate];
}
  • Does this work? Won't the NSFetchedResultsController end up sorting the trMonthYear property as a string? So "April 2017" < "June 2016"? – Z S Oct 06 '17 at 11:46