What I want to do seems pretty simple, but I can't find any answers on the web. I have an NSMutableArray of objects, let's say they are 'Person' objects. I want to sort the NSMutable array by Person.birthDate which is an NSDate.
I think it has something to do with this method:
NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(???)];
In Java I would make my object implement Comparable, or use Collections.sort with an inline custom comparator...how on earth do you do this in Objective-C?
Source: Tips4all
Compare method
ReplyDeleteEither you implement a compare-method for your object:
- (NSComparisonResult)compare:(Person *)otherObject {
return [self.birthDate compare:otherObject.birthDate];
}
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(compare:)];
NSSortDescriptor (better)
or usually even better: (The default sorting selector of NSSortDescriptor is compare:)
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate"
ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];
Blocks (shiny!)
There's also the possibility of sorting with a block since 10.6:
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingComparator:^(id a, id b) {
NSDate *first = [(Person*)a birthDate];
NSDate *second = [(Person*)b birthDate];
return [first compare:second];
}];
See the NSMutableArray method sortUsingFunction:context:
ReplyDeleteYou will need to set up a compare function which takes two objects (of type Person, since you are comparing two Person objects) and a context parameter.
The two objects are just instances of Person. The third object is a string, e.g. @"birthDate".
This function returns an NSComparisonResult: It returns NSOrderedAscending if PersonA.birthDate < PersonB.birthDate. It will return NSOrderedDescending if PersonA.birthDate > PersonB.birthDate. Finally, it will return NSOrderedSame if PersonA.birthDate == PersonB.birthDate.
This is rough pseudocode; you will need to flesh out what it means for one date to be "less", "more" or "equal" to another date:
NSComparisonResult compare(Person *firstPerson, Person *secondPerson, void *context) {
if ([firstPerson birthDate] < [secondPerson birthDate])
return NSOrderedAscending;
else if ([firstPerson birthDate] > [secondPerson birthDate])
return NSOrderedDescending;
else
return NSOrderedSame;
}
I did this in iOS 4 using a block.
ReplyDeleteHad to cast the elements of my array from id to my class type.
In this case it was a class called Score with a property called points.
Also you need to decide what to do if the elements of your array are not the right type, for this example I just returned NSOrderedSame, however in my code I though an exception.
NSArray *sorted = [_scores sortedArrayUsingComparator:^(id obj1, id obj2){
if ([obj1 isKindOfClass:[Score class]] && [obj2 isKindOfClass:[Score class]]) {
Score *s1 = obj1;
Score *s2 = obj2;
if (s1.points > s2.points) {
return (NSComparisonResult)NSOrderedAscending;
} else if (s1.points < s2.points) {
return (NSComparisonResult)NSOrderedDescending;
}
}
// TODO: default is the same?
return (NSComparisonResult)NSOrderedSame;
}];
return sorted;
PS: This is sorting in descending order.
Your Person objects need to implement a method, say compare: which takes another Person object, and return NSComparisonResult according to the relationship between the 2 objects.
ReplyDeleteThen you would call sortedArrayUsingSelector: with @selector(compare:) and it should be done.
There are other ways, but as far as I know there is no Cocoa-equiv of the Comparable interface. Using sortedArrayUsingSelector: is probably the most painless way to do it.
Starting in iOS 4 you can also use blocks for sorting.
ReplyDeleteFor this particular example I'm assuming that the objects in your array have a 'position' method, which returns an NSInteger.
NSArray *arrayToSort = where ever you get the array from... ;
NSComparisonResult (^sortBlock)(id, id) = ^(id obj1, id obj2) {
if ([obj1 position] > [obj2 position]) {
return (NSComparisonResult)NSOrderedDescending;
}
if ([obj1 position] < [obj2 position]) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
};
NSArray *sorted = [arrayToSort sortedArrayUsingComparator:sortBlock];
Note: the "sorted" array will be autoreleased.
I tried all, but this worked for me. In a class I have another class named "crimeScene", and want to sort by a property of "crimeScene".
ReplyDeleteThis works like a charm!
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:@"crimeScene.distance" ascending:YES];
[self.arrAnnotations sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
iOS 4 blocks will save you :)
ReplyDeletefeaturesArray = [[unsortedFeaturesArray sortedArrayUsingComparator: ^(id a, id b) {
DMSeatFeature *first = ( DMSeatFeature* ) a;
DMSeatFeature *second = ( DMSeatFeature* ) b;
if ( first.quality == second.quality )
return NSOrderedSame;
else
{
if ( eSeatQualityGreen == m_seatQuality
|| eSeatQualityYellowGreen == m_seatQuality
|| eSeatQualityDefault == m_seatQuality )
{
if ( first.quality < second.quality )
return NSOrderedAscending;
else
return NSOrderedDescending;
}
else // eSeatQualityRed || eSeatQualityYellow
{
if ( first.quality > second.quality )
return NSOrderedAscending;
else
return NSOrderedDescending;
}
}
}] retain];
http://sokol8.blogspot.com/2011/04/sorting-nsarray-with-blocks.html a bit of description
there is a missing step in gs 2nd answer, works fine then.
ReplyDeleteNSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate"
ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptor:sortDescriptors];
(I did not write in the comment because I just logged in and without reputation, I can't comment :-/)
For some reason the topmost answer didn't work for me. I was getting a warning on compile and it crashed at runtime. Here the code I used:
ReplyDeleteNSSortDescriptor *dateSorter = [[NSSortDescriptor alloc] initWithKey:@"Date" ascending:YES];
[filteredShowsList sortUsingDescriptors:[NSArray arrayWithObject:dateSorter]];
Note that filteredShowsList has to be a NSMutableArray, not a NSArray!
NSSortDescriptor *sortDescriptor;
ReplyDeletesortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];
thanks its working fine...
For NSMutableArray, use the sortUsingSelector method. It sorts it-place, without creating a new instance.
ReplyDeletei've used sortUsingFunction:: in some of my projects
ReplyDeleteint SortPlays(id a, id b, void* context)
{
Play* p1=a;
Play* p2=b;
if (p1.score<p2.score) return NSOrderedDescending;
else if (p1.score>p2.score) return NSOrderedAscending;
return NSOrderedSame;
}
...
[validPlays sortUsingFunction:SortPlays context:nil];
Sorting NSMutableArray is very simple..
ReplyDeleteNSMutableArray *arrayToFilter = [[NSMutableArray arrayWithObjects:@"Photoshop", @"Flex", @"AIR",@"Flash", @"Acrobat", nil] autorelease];
NSMutableArray *productsToRemove = [[NSMutableArray array] autorelease];
for (NSString *products in arrayToFilter) {
if (fliterText && [products rangeOfString:fliterText options:NSLiteralSearch|NSCaseInsensitiveSearch].length == 0)
[productsToRemove addObject:products];
}
[arrayToFilter removeObjectsInArray:productsToRemove];