Sorting core data position changes with sort descriptors for iPhone
Asked Answered
G

2

5

I have a CoreData entity with two attributes. One called 'position' the other called 'positionChange'. Both of them are integers where the position attribute is the current position and the positionChange is the difference between the previous position and the new position. This means that the positionChange can be negative.

Now I would like to sort by positionChange. But I would like it to disregard the negative values. Currently I'm sorting it descending, which will give the result: 2, 1, 0, -1, -2. But what I'm looking for is to get this result: 2, -2, 1, -1, 0.

Any ideas on how to solve this using sort descriptors?


EDIT

I got 2 classes, one called DataManager and the other containing my NSNumber category (positionChange is of type NSNumber).

In DataManager I have a method called 'fetchData:' where I'm executing my fetch request with a sort descriptor:

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Entity" inManagedObjectContext:managedObjectContext];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"positionChange" ascending:NO selector:@selector(comparePositionChange:)];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

I'm doing some more stuff to the request, but that's not interesting for this issue.

My NSNumber category should be exactly like the one you posted: In the .h:

@interface NSNumber (AbsoluteValueSort)
- (NSComparisonResult)comparePositionChange:(NSNumber *)otherNumber;
@end

And in the .m:

@implementation NSNumber (AbsoluteValueSort)

- (NSComparisonResult)comparePositionChange:(NSNumber *)otherNumber
{
    return [[NSNumber numberWithFloat:fabs([self floatValue])] compare:[NSNumber numberWithFloat:fabs([otherNumber floatValue])]];
}

@end

When I call fetchData on my DataManager object I get this error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unsupported NSSortDescriptor selector: comparePositionChange:'

Any ideas of what might be the case? I have included my NSNumber category header file in my DataManager class.

Gristmill answered 30/6, 2011 at 13:3 Comment(1)
Can you add an absolutePositionChange field to the model and populate it (in the setter for positionChange, if there is one) with abs(positionChange)? You could use that field in your sort descriptor.Diley
C
2

Try this, assuming your positionChange is an NSNumber:

@interface NSNumber (AbsoluteValueSort)

-(NSComparisonResult) comparePositionChange:(NSNumber *)otherNumber;

@end

followed by:

@implementation NSNumber (AbsoluteValueSort)

-(NSComparisonResult) comparePositionChange:(NSNumber *)otherNumber
{
    return [[NSNumber numberWithFloat: fabs([self floatValue])] compare:[NSNumber numberWithFloat: fabs([otherNumber floatValue])]];
}

@end

and then do this:

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"positionChange" ascending:NO selector:@selector(comparePositionChange:)];

and sort with that descriptor. If you want to make it so that -2 is always after 2, or 5 is always after -5 you can modify comparePositionChange: to return NSOrderedAscending in the cases when one number is the negative of the other.

Coburg answered 30/6, 2011 at 13:24 Comment(7)
Is it possible to call a category method between objects? What I mean is that my sort descriptor is located in a different class than the NSNumber category. Calling comparePositionChange: with your example above won't work. You see my point?Gristmill
Just include whatever file has the NSNumber category in it and it should work. The category is not "in" any class.Coburg
The category is within the NSNumber class. Calling it with your example will imply the category is implemented within the NSSortDescriptor class. Otherwise the application will crash with NSInvalidArgumentException with reason: 'unsupported selector'. A simple include will not work in this case. Unless I'm doing something wrong. Have you tested your solution?Gristmill
I used the sort descriptor to sort an already-filled array of objects in a .m file, with the category in its .h file. It worked. The documentation for sortDescriptorWithKey:ascending:selector: says "The selector must specify a method implemented by the value of the property identified by keyPath." So it expects a method implemented by NSNumber, not NSSortDescriptor.Coburg
Hey, I have edited my question. Wasn't allowed to post such a long post or post a new answer. Could you please have a look and see if you can spot any errors? Thanks!Gristmill
I got the same error. I guess you can't use custom selectors to sort persistent data directly. You'll have to sort after you fetch the data - I incorrectly assumed that both ways would work.Coburg
Cool. I got it working now sorting the results array instead of the fetch request. Thanks alot for your help!Gristmill
B
5

The reason that code fails with the unsupported NSSortDescriptor selector error is that you're using a SQLite data store and attempting to use a Cocoa method as the sort descriptor. With a SQLite store, sort descriptors are translated on the fly into SQL and sorting is done by SQLite. That really helps performance but it also means that sorting is done in a non-Cocoa environment where your custom comparison method doesn't exist. Sorting like this only works for common, known methods and not for arbitrary Cocoa code.

A simple fix would be to do the fetch without sorting, get the results array, and sort that. Arrays can be sorted using whatever Cocoa methods you want, so med200's category should be useful there.

You could also change from a SQLite data store to a binary store, if your data set isn't very large.

Batfowl answered 30/6, 2011 at 16:43 Comment(0)
C
2

Try this, assuming your positionChange is an NSNumber:

@interface NSNumber (AbsoluteValueSort)

-(NSComparisonResult) comparePositionChange:(NSNumber *)otherNumber;

@end

followed by:

@implementation NSNumber (AbsoluteValueSort)

-(NSComparisonResult) comparePositionChange:(NSNumber *)otherNumber
{
    return [[NSNumber numberWithFloat: fabs([self floatValue])] compare:[NSNumber numberWithFloat: fabs([otherNumber floatValue])]];
}

@end

and then do this:

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"positionChange" ascending:NO selector:@selector(comparePositionChange:)];

and sort with that descriptor. If you want to make it so that -2 is always after 2, or 5 is always after -5 you can modify comparePositionChange: to return NSOrderedAscending in the cases when one number is the negative of the other.

Coburg answered 30/6, 2011 at 13:24 Comment(7)
Is it possible to call a category method between objects? What I mean is that my sort descriptor is located in a different class than the NSNumber category. Calling comparePositionChange: with your example above won't work. You see my point?Gristmill
Just include whatever file has the NSNumber category in it and it should work. The category is not "in" any class.Coburg
The category is within the NSNumber class. Calling it with your example will imply the category is implemented within the NSSortDescriptor class. Otherwise the application will crash with NSInvalidArgumentException with reason: 'unsupported selector'. A simple include will not work in this case. Unless I'm doing something wrong. Have you tested your solution?Gristmill
I used the sort descriptor to sort an already-filled array of objects in a .m file, with the category in its .h file. It worked. The documentation for sortDescriptorWithKey:ascending:selector: says "The selector must specify a method implemented by the value of the property identified by keyPath." So it expects a method implemented by NSNumber, not NSSortDescriptor.Coburg
Hey, I have edited my question. Wasn't allowed to post such a long post or post a new answer. Could you please have a look and see if you can spot any errors? Thanks!Gristmill
I got the same error. I guess you can't use custom selectors to sort persistent data directly. You'll have to sort after you fetch the data - I incorrectly assumed that both ways would work.Coburg
Cool. I got it working now sorting the results array instead of the fetch request. Thanks alot for your help!Gristmill

© 2022 - 2024 — McMap. All rights reserved.