开发者

NSFetchedResultsController section ordering using display order

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
                     开发者_StackOverflow                                                                               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?


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


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


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!


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/

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜