Custom selector for NSSortDescriptor and NSFetchedResultsController

I ran into small problems, and I was wondering if anyone could give some recommendations.

I am trying to create section headers with the following order: ["Push", "Busy", "Finished", "Canceled"].

Unfortunately, this order cannot be implemented with a simple "upstream YES / NO" ... I need to add a custom comparison selector ... something like this ..:

** Where "states" are displayed in ["Push", "Busy", "Finished", "Canceled"]

NSSortDescriptor *sortStates = [NSSortDescriptor sortDescriptorWithKey:@"states" ascending:NO selector:@selector(compareCustom:)]; 

Therefore, I implemented "compareCustom" ... however, it turned out that I could not implement my own sorter as the following exception results:

*** Application termination due to an undetected exception "NSInvalidArgumentException", reason: "unsupported selector NSSortDescriptor: compareCustom:

For reference, sortDate is implemented:

 request.sortDescriptors = @[sortStates] self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:@"states" cacheName:nil]; 

So, it looks like I cannot implement compareCustom, which returns NSComparisonResult? I feel it should be possible. But I canโ€™t figure out how to do this. If anyone can give some advice, I would really appreciate it!

Also for reference, here is my compareCustom method

 -(NSComparisonResult)compareCustom:(NSString *)anotherState { if ([(NSString *)self isEqualToString:@"Push"] && [anotherState isEqualToString:@"Canceled"]) { return NSOrderedDescending; } else if ([(NSString *)self isEqualToString:@"Push"] && [anotherState isEqualToString:@"Busy"]) { return NSOrderedDescending; } else if ([(NSString *)self isEqualToString:@"Push"] && [anotherState isEqualToString:@"Finished"]) { return NSOrderedDescending; } else if ([(NSString *)self isEqualToString:@"Push"] && [anotherState isEqualToString:@"Push"]) { return NSOrderedSame; } else if ([(NSString *)self isEqualToString:@"Finished"] && [anotherState isEqualToString:@"Canceled"]) { return NSOrderedDescending; } else if ([(NSString *)self isEqualToString:@"Finished"] && [anotherState isEqualToString:@"Busy"]) { return NSOrderedAscending; } else if ([(NSString *)self isEqualToString:@"Finished"] && [anotherState isEqualToString:@"Finished"]) { return NSOrderedSame; } else if ([(NSString *)self isEqualToString:@"Finished"] && [anotherState isEqualToString:@"Push"]) { return NSOrderedAscending; } else if ([(NSString *)self isEqualToString:@"Busy"] && [anotherState isEqualToString:@"Finished"]) { return NSOrderedDescending; } else if ([(NSString *)self isEqualToString:@"Busy"] && [anotherState isEqualToString:@"Canceled"]) { return NSOrderedDescending; } else if ([(NSString *)self isEqualToString:@"Busy"] && [anotherState isEqualToString:@"Busy"]) { return NSOrderedSame; } else if ([(NSString *)self isEqualToString:@"Busy"] && [anotherState isEqualToString:@"Push"]) { return NSOrderedAscending; } else { return NSOrderedSame; } } 
+4
ios objective-c core-data
source share
2 answers

According to the documentation, custom sorting does not work with SQLite persistent storage:

SQL storage, on the other hand, compiles the predicate and sorts the descriptors for SQL and evaluates the result in the database itself. This is primarily done for performance, but this means that the evaluation takes place in a non-Cocoa environment and therefore sorts the descriptors (or predicates) that rely on Cocoa cannot work. Supported sort selectors are compared: and caseInsensitiveCompare :, localizedCompare :, localizedCaseInsensitiveCompare :, and localizedStandardCompare: (the latter is sorting like Finder, and that most people should use most of the time). Also, you cannot sort by transient properties using SQLite repository.

So it seems you can't just get NSFetchedResultsController display sections in alphabetical order.

But you can use a workaround. Add an additional, constant attribute of integer type to your object (call it sectionOrder ). Set its value according to the states property (so that it will be 0 for "Push" , 1 for "Busy" , etc.). You can do this, for example, in the awakeFromInsert method.

Then use @"sectionOrder" as both sectionNameKeyPath and the sectionNameKeyPath path in the sort descriptor. You can customize the section headers to "Push", "Busy", etc. Using this UITableViewDataSource method:

 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section]; return [sectionInfo.objects.firstObject name]; } 

Loans for this great answer .

+5
source share

The NSSortDescriptor constructor that you use is trying to invoke the specified selector for objects stored in the property named @"states" . I'm not sure what that value is, but it looks like they can be NSSortDescriptor objects based on the error you see.

Instead of trying to find an object selector that may or may not exist, use the block constructor:

You should also consider rewriting your comparison with integers or something to make it smaller:

 static NSArray *stateOrder = @[@"Push", @"Busy", @"Finished", @"Cancelled"]; NSSortDescriptor *sortStates = [NSSortDescriptor sortDescriptorWithKey:@"states" ascending:NO comparator:^(id obj1, id obj2) { NSInteger state1 = [stateOrder indexOfObject:obj1]; NSInteger state2 = [stateOrder indexOfObject:obj2]; if (state1 < state2) { return (NSComparisonResult)NSOrderedAscending; } else if (state1 > state2) { return (NSComparisonResult)NSOrderedDescending; } return (NSComparisonResult)NSOrderedSame; }]; 
+4
source share

All Articles