How do I sort an NSMutableArray with custom objects in it?

478,725

Solution 1

Compare method

Either you implement a compare-method for your object:

- (NSComparisonResult)compare:(Person *)otherObject {
    return [self.birthDate compare:otherObject.birthDate];
}

NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(compare:)];

NSSortDescriptor (better)

or usually even better:

NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"birthDate"
                                           ascending:YES];
NSArray *sortedArray = [drinkDetails sortedArrayUsingDescriptors:@[sortDescriptor]];

You can easily sort by multiple keys by adding more than one to the array. Using custom comparator-methods is possible as well. Have a look at the documentation.

Blocks (shiny!)

There's also the possibility of sorting with a block since Mac OS X 10.6 and iOS 4:

NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingComparator:^NSComparisonResult(Person *a, Person *b) {
    return [a.birthDate compare:b.birthDate];
}];

Performance

The -compare: and block-based methods will be quite a bit faster, in general, than using NSSortDescriptor as the latter relies on KVC. The primary advantage of the NSSortDescriptor method is that it provides a way to define your sort order using data, rather than code, which makes it easy to e.g. set things up so users can sort an NSTableView by clicking on the header row.

Solution 2

See the NSMutableArray method sortUsingFunction:context:

You 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 (such as comparing seconds-since-epoch etc.):

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

If you want something more compact, you can use ternary operators:

NSComparisonResult compare(Person *firstPerson, Person *secondPerson, void *context) {
  return ([firstPerson birthDate] < [secondPerson birthDate]) ? NSOrderedAscending : ([firstPerson birthDate] > [secondPerson birthDate]) ? NSOrderedDescending : NSOrderedSame;
}

Inlining could perhaps speed this up a little, if you do this a lot.

Solution 3

I did this in iOS 4 using a block. Had 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.

Solution 4

Starting in iOS 4 you can also use blocks for sorting.

For 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.

Solution 5

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".

This works like a charm:

NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:@"crimeScene.distance" ascending:YES];
[self.arrAnnotations sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
Share:
478,725
Tetaxa
Author by

Tetaxa

Java, Objective-C, Rails developer...in roughly that order.

Updated on July 08, 2022

Comments

  • Tetaxa
    Tetaxa almost 2 years

    What I want to do seems pretty simple, but I can't find any answers on the web. I have an NSMutableArray of objects, and let's say they are 'Person' objects. I want to sort the NSMutableArray 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?

  • Stefan
    Stefan almost 15 years
    Using sortUsingFunction:context: is probably the most c-ish way and definitly the most unreadable one.
  • Stefan
    Stefan almost 15 years
    There's nothing really wrong with it, but I think there are now much better alternatives.
  • Alex Reynolds
    Alex Reynolds almost 15 years
    Perhaps, but I don't think it would be any less readable to someone from a Java background who might be looking for something similar to Java's abstract Comparator class, which implements compare(Type obj1, Type obj2).
  • freespace
    freespace almost 15 years
    I think the biggest stumbling block to using sortUsingFunction for some one with out a solid C background would be: a) realising it wants a function pointer; b) get used to the idea of a function pointer; c) parse the function pointer so as to construct the required function.
  • Alex Reynolds
    Alex Reynolds almost 15 years
    I get the sense a couple of you are looking for any reason whatsoever to criticize this perfectly fine answer, even if that criticism has very little technical merit. Weird.
  • CIFilter
    CIFilter almost 15 years
    The method call is actually "sortedArrayUsingDescriptors:", with an 's' at the end.
  • Dan Rosenstark
    Dan Rosenstark over 13 years
    @Georg, the problem with your answer above, and the advantages to Alex's method here, is that you get one compare method per class. So if you need to compare on multiple criteria, you must use sortUsingFunction:content:, Right?
  • Stefan
    Stefan over 13 years
    @Yar: Either you can use the solution I provided in the first paragraph, or you use multiple sort descriptors. sortedArrayUsingDescriptors: takes an array of sort descriptors as argument.
  • Martin Gjaldbaek
    Martin Gjaldbaek almost 13 years
    The first example has a bug: You compare the birthDate instance variable in one object with the other object itself, rather than its birthDate variable.
  • Stefan
    Stefan almost 13 years
    @Martin: Thanks! Funny that nobody else noticed before I got 75 upvotes for it.
  • jpswain
    jpswain over 12 years
    You don't actually need the "(Score *)" casts in there, you can just do "Score *s1 = obj1;" because id will happily cast to anything without warning from the compiler :-)
  • jpswain
    jpswain over 12 years
    Because this is the accepted answer, and therefore probably considered definitive by most users, it might be helpful to add a 3rd, block-based example so that users will be aware it exists too.
  • Stefan
    Stefan over 12 years
    @orange80: I tried that. I don't own a Mac any more, so it would be nice if you could look at the code.
  • fishinear
    fishinear over 12 years
    You can also declare the parameters of the block with the correct type already, reducing the sorting to: [drinkDetails sortedArrayUsingComparator:^(Person* a, Person* b) { return [a.birthDate compare:b.birthDate]; }]
  • Stefan
    Stefan over 12 years
    @fishinear: Are you sure? I've haven't used blocks yet myself, but looking at other examples it seems that everyone uses id as the arguments. Example: Question 1, Question 2.
  • TPoschel
    TPoschel about 12 years
    In the case where birthDate is null and you want those to show up at the end of the sorted array you can do this: NSDate *first = [(Person *)a birthDate] ? [a birthDate]:[NSDate distantFuture];
  • thesummersign
    thesummersign almost 12 years
    right orange80 downcasting doesn't requires a cast before the weak variable.
  • Lithu T.V
    Lithu T.V almost 12 years
    so how about case insensitive sort on an nsstring object comparison using second method??
  • Stefan
    Stefan almost 12 years
    @Lithu: Did you look at the documentation. It's basically directly in there. If you don't find out how to do it, ask in a new question.
  • Lithu T.V
    Lithu T.V almost 12 years
    @GeorgSchölly : yup worked it out by docs..frgt to update the cmnt :)
  • Lithu T.V
    Lithu T.V almost 12 years
    k here is the answer for wht i hav asked ,ie about case sensitive nd case insensitive sort.there is one more method in sort descriptor which defines the selector :- sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease];
  • kubilay
    kubilay almost 12 years
    Thank you! That's a solution for issue about ordering accented characters in SQLite.
  • Stephan
    Stephan over 11 years
    If you have a NSMutableArray i prefer to use the methodes sortUsingDescriptors , sortUsingFunction or sortUsingSelector. As far as the array is mutable I usually don't need a sorted copy.
  • Scott Corscadden
    Scott Corscadden over 11 years
    You should sort nil vs. not-nil to the top or bottom consistently, so the default end return might be return ((!obj1 && !obj2) ? NSOrderedSame : (obj1 ? NSOrderedAscending : NSOrderedDescending))
  • Nikesh K
    Nikesh K over 11 years
    heh Chris, i tried this code, I do hv a refresh in my program.. for the first time i does correct job, got a descending order output.. but when i refresh.( execute the same code with same data ) it changed the order, it was not descending.. Say i hv 4 objects in my array, 3 hv same data, 1 is differed.
  • yannis
    yannis over 11 years
    @Jeroen Hi! When you are going to reject an edit, just reject it, don't click "improve" if you don't actually mean to improve the edit. Rejecting the edits here was a good call, but by going through improve you've created two nonsensical revisions to the question.
  • Asciiom
    Asciiom over 11 years
    I know, you are right, I had a moment of confusion here because of doing 3 things at once. Won't happen again ;)
  • Hiren
    Hiren over 11 years
    I have used block option for sort. it's work great. but i have another problem i have to sort an array using condition. like i have two values "Company" and "Date". if the company name is same then it check date which would be latest it first sort can you help me out ?
  • vikingosegundo
    vikingosegundo over 11 years
    why to pass in a mutable array, when a new array is returned. why to create a wrapper at all?
  • marciokoko
    marciokoko about 11 years
    What if I have an array with latitude and longitude and I want to sort by distance from userLocation. I've calculated the distance to each. Do I create a new array with the distance value?
  • Stefan
    Stefan about 11 years
    @marciokoko: Open a new questions, comments are not a good place for this. :)
  • marciokoko
    marciokoko about 11 years
    Thanks Georg, here it is: stackoverflow.com/questions/14879486/…
  • Accatyyc
    Accatyyc about 11 years
    Just wanted to add - the answer to the question asked by @HiRen isn't for another question. This is exactly what the sort descriptors in this example are for. Maybe this post should explain that you can use multiple sort descriptors to sort by many keys?
  • Stefan
    Stefan about 11 years
    @Accatyyc: I added a small paragraph about it. I didn't want to increase the complexity of the answer too much.
  • Nubzor
    Nubzor almost 11 years
  • jmathew
    jmathew over 10 years
    Just an update: I too was looking for something that sorted the mutable array in place, there are now "sortUsing" equivalent methods for all the "sortedArrayUsing" methods as of iOS 7. Such as sortUsingComparator:.
  • gnasher729
    gnasher729 about 10 years
    If you actually expect objects that are not of the "Score" class, you need to sort them a bit more careful. Otherwise you have a situation where other == score1 < score2 == other which is inconsistent and could lead to trouble. You could return a value that implies Score objects sort before all other objects, and all other objects sort equal to each other.
  • Matthew Knippen
    Matthew Knippen about 10 years
    this should be updated to include literals, so future people looking at this code can see updated syntax, and how it's now being done.
  • bcattle
    bcattle about 10 years
    Not to beat a dead horse but its potentially be worth noting that sortedUsingComparator sorts in-place with a NSMutableArray, potentially a more common use case.
  • AlKozin
    AlKozin about 10 years
    And what is the best/fastest sorting method?
  • KarenAnne
    KarenAnne about 10 years
    The third one is the best option? @GeorgSchölly
  • Stefan
    Stefan about 10 years
    @Karen: It depends. Use the compare method if there is a natural ordering between your objects. If it's a simple comparison, use a block. And finally if your comparison is tricky and uses lots of logic, an NSSortDescriptor probably makes the most sense.
  • Septronic
    Septronic over 8 years
    I love the block as well, I've used it in a couple of projects I was working on, and they never failed me! Block to the rescue! :) ( and Georg )
  • Erika Electra
    Erika Electra almost 8 years
    Where does the compare method go? In the implementation of the custom object being sorted in the NSMutableArray, or in the implementation of the class doing the sorting? I sense it should be in the custom object's implementation (from the self.birthdate), but the formatting of the answer makes it seem like it's the same .m file as the call to sort the array, so I wanted to check to make sure. From the standpoint of a programming beginner, it's not obvious at all where to put snippets of code even if it might seem second nature to a vet.
  • Stefan
    Stefan almost 8 years
    You're right, it goes into the implementation of the custom object.
  • Peter Mortensen
    Peter Mortensen over 6 years
    You ought to call it "LINQ to Objective-C".
  • Ricardo
    Ricardo almost 4 years
    The question is about NSMutableArray, not about Arrays collection in Swift