10

I currently have an option that allows a user to change the display order of a category in my iPhone app.

I want to section the table view using a NSFetchedResultsController so that the section titles are the "category.name" ordered by "category.displayOrder" where "category" has a TO-ONE relationship with the entity I am fetching. The only way I can get the sectioning to work correctly is by using "category.displayOrder" as the section title.

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"category.displayOrder" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];

NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[fetchRequest setFetchBatchSize:10];

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

Any ideas on how I can name the section title something different then the property I am sorting with?

Matthias Bauch
  • 89,811
  • 20
  • 225
  • 247
avenged
  • 567
  • 1
  • 8
  • 19

4 Answers4

15

Not sure if I understand your question completely, but I do something similar in my app and here's how I get it to work:

Firstly, the fetchedResultsController method, where I set the sort descriptions and predicates based on what I am trying to do. In this case I want to sort movie titles by release date THEN by name. Then with my predicate I grab entities of a specific 'type' and within a certain 'releaseDate' range.

In my fetchresultscontroller definition, you set the sectionNameKeyPath to "releaseDate" so my section headers will be based on a date.

- (NSFetchedResultsController *)fetchedResultsController {

    if (fetchedResultsController_ != nil) {
        return fetchedResultsController_;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSSortDescriptor *sortByReleaseDate = [[NSSortDescriptor alloc] initWithKey:@"releaseDate" ascending:NO];
    NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortByReleaseDate,sortByName, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];
    [sortDescriptors release];
    [sortByName release];
    [sortByReleaseDate release];            
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(type == 'Movies') AND (releaseDate <= %@) AND (releaseDate >= %@)", [NSDate date], [NSDate dateWithTimeIntervalSinceNow:kOneDayTimeInterval*-30]];
    [fetchRequest setPredicate:predicate];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Movie" inManagedObjectContext:self.managedObjectContext];
        [fetchRequest setEntity:entity];

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

    ...// Perform and return fetch here, error handling etc...

    return fetchedResultsController_;

}    

Then in my table view data source delegate methods, I return the actual title for each header after transforming my NSDate into NSString (remember you have to return NSString for a tableview header title.

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    NSString *rawDateStr = [[[self.fetchedResultsController sections] objectAtIndex:section] name];
    //convert default date string to NSDate...
    NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss ZZ"];
    NSDate *date = [formatter dateFromString:rawDateStr];

    //convert NSDate to format we want...
    [formatter setDateFormat:@"d MMMM yyyy"];
    NSString *formattedDateStr = [formatter stringFromDate:date];
    return formattedDateStr;

}

So if I wanted to change the way my data is being displayed to be organised by titleName for instance, I'd change my fetchedResultsController object to:

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

And modify my tableview:titleForHeaderInSection: data source method to simply return the titleName (which is already a string):

 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

        return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

I hope this helps you find a solution to your specific problem.

Cheers, Rog

Rog
  • 18,602
  • 6
  • 76
  • 97
  • 1
    I am trying to sort my sections by displayOrder (NSInteger), but instead of displaying the integer as the section title, I would like to display the name that corresponds to that displayOrder. So instead of having section titles 1, 2, and 3, I would have "FirstName", "Second Name", and "Third Name". Do you think the best way is to determine the title based on the display order in titleForHeaderInSection? – avenged Dec 08 '10 at 10:09
  • It's the only way as far as I am aware. You can do a switch on displayOrder and then return the corresponding section you want displayed. – Rog Dec 08 '10 at 11:15
  • I was hoping that I wouldn't have to perform another fetch within titleForHeaderInSection, but it doesn't seem like there is any other way... – avenged Dec 08 '10 at 19:46
11

I had the same problem and I've another solution - use a transient property for the sectionNameKeyPath i.e. category.sectionName. It seems that if you use a core data property as a sectionNameKeyPath it will try and sort with that.

So using your example above:

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"category.displayOrder" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

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

Category.h:

@interface Category : NSManagedObject

@property (nonatomic, retain) NSString* name;
@property (nonatomic, retain) NSNumber* displayOrder;

-(NSString*) sectionName;

@end

Category.m:

@implementation Category

@dynamic name;
@dynamic displayOrder;

-(NSString*) sectionName{
    return self.name;
}

@end

Then in your view controller:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    id<NSFetchedResultsSectionInfo> sectionInfo=[self.resultsController.sections objectAtIndex:section];
    return sectionInfo.name;
}
Mark Horgan
  • 3,243
  • 4
  • 27
  • 28
3

Thanks to Rog I was able to wrap my head around this problem and come up with a solution. It's not as clean as I would want it so if anyone has a better approach I would love to hear about it.

Based on what I have learned (I could be wrong, please let me know) you can only name sections (sectionNameKeyPath) by the property in which you are ordering your table. In my case, I wanted to sort my table using a property called displayOrder, but I didn't want my sections titles to be 0, 1, 2, etc. Instead I wanted the section titles to use the title property "Name1", "Name2", etc. that corresponds to the displayOrder. To do this I determine the header title based on the displayOrder in titleForHeaderInSection:

        NSString *displayOrder = [sectionInfo name];

        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:[NSEntityDescription entityForName:@"Platform" inManagedObjectContext:managedObjectContext]];

        NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
        [numberFormatter setNumberStyle:NSNumberFormatterNoStyle];

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"displayOrder == %@", [numberFormatter numberFromString:displayOrder]];
        [fetchRequest setPredicate:predicate];

        Platform *platform;
        NSError *error = nil;
        NSArray *fetchResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
        if ([fetchResults count] > 0) {
            platform = [fetchResults objectAtIndex:0];
            headerView.titleLabel.text = [platform name];
        } else {
            headerView.titleLabel.text = @"Unknown";
        }

        [numberFormatter release];
        [fetchRequest release];

Thanks Rog!

avenged
  • 567
  • 1
  • 8
  • 19
  • @avenged, how in the world did you find that out? "Based on what I have learned (I could be wrong, please let me know) you can only name sections (sectionNameKeyPath) by the property in which you are ordering your table." That was killing me! Thanks! – Mr Rogers Oct 07 '11 at 23:21
0

In my case I had to RTFM:

- initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:

If this sectionNameKeyPath is not the same as that specified by the first sort descriptor in fetchRequest, they must generate the same relative orderings. For example, the first sort descriptor in fetchRequest might specify the key for a persistent property; sectionNameKeyPath might specify a key for a transient property derived from the persistent property.

Which was creating inconsistencies in the Table View ordering when either the sectionNameKeyPath or NSSortDescriptor property of the managed object changed to a different sort order than the other property.

Source:
https://richardwarrender.com/2010/10/core-data-objects-in-wrong-sections/

pkamb
  • 33,281
  • 23
  • 160
  • 191