How to sort an NSArray with objects - ios

I have a NSArray of objects. Each object has several properties.
For example:
The NSArray oneArray has 5 objects. Each object has the following properties: name, address, ZIP_Code.
How can I sort the NSArray, by name?

NSSortDescriptor* dx = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor* dy = [[NSSortDescriptor alloc] initWithKey:#"address" ascending:NO selector:#selector(caseInsensitiveCompare:)];
[arr sortUsingDescriptors:[NSArray arrayWithObjects:dx, dy, nil]];
[dx release];
[dy release];
Taken from code posted by KennyTM. Edited by Jordan to actually work ;) Replace Array arr with your array. Modify to suit.

NSInteger nameSort(id obj1, id obj2, void *context)
{
NSComparisonResult result = [obj1.name compare:obj2.name];
if (result == NSOrderedAscending) // stringOne < stringTwo
return NSOrderedAscending;
if (result == NSOrderedDescending) // stringOne > stringTwo
return NSOrderedDecending;
if (result == NSOrderedSame) // stringOne == stringTwo
return NSOrderedSame;
}
/*to sort oneArray*/ oneArray = [oneArray sortedArrayUsingFunction:nameSort context:NULL];

I like Jordan's answer because (1) I didn't know about NSSortDescriptor and (2) it's useful for sorting on multiple properties.
But what I usually do is create a method like -(NSComparisionResult)compare:(MyClass*)otherObject in my class, then use -[myArray sortedArrayUsingSelector:#selector(compare:)]. The compare method itself is similar to Jumhyn's answer, but I think is a little cleaner because the class itself compares the objects, instead of a stand-alone function.

Related

Custom NSSortDescriptor for NSFetchedResultsController

I have a custom "sort" that I currently perform on a set of data. I now need to perform this sort on the fetchedObjects in the NSFetchedResultsController. All of our table data works hand in hand with core data fetched results so replacing the data source with a generic array has been problematic.
Since NSFetchedResultsController can take NSSortDescriptors it seems like that is the best route. The problem is I don't know how to convert this sort algorithm into an custom comparator.
How do I convert this into a custom comparator (if possible)? (if not how do I get the desired sorted result while using NSFetchedResultsController). In essence the field 'priority' can be either 'high' 'normal' or 'low' and the list needs to be sorted in that order.
+(NSArray*)sortActionItemsByPriority:(NSArray*)listOfActionitemsToSort
{
NSMutableArray *sortedArray = [[NSMutableArray alloc]initWithCapacity:listOfActionitemsToSort.count];
NSMutableArray *highArray = [[NSMutableArray alloc]init];
NSMutableArray *normalArray = [[NSMutableArray alloc]init];
NSMutableArray *lowArray = [[NSMutableArray alloc]init];
for (int x = 0; x < listOfActionitemsToSort.count; x++)
{
ActionItem *item = [listOfActionitemsToSort objectAtIndex:x];
if ([item.priority caseInsensitiveCompare:#"high"] == NSOrderedSame)
[highArray addObject:item];
else if ([item.priority caseInsensitiveCompare:#"normal"] == NSOrderedSame)
[normalArray addObject:item];
else
[lowArray addObject:item];
}
[sortedArray addObjectsFromArray:highArray];
[sortedArray addObjectsFromArray:normalArray];
[sortedArray addObjectsFromArray:lowArray];
return sortedArray;
}
UPDATE
Tried using a NSComparisonResult block but NSFetchedResultsController does not allow that
Also tried using a transient core data attribute and then calculating a field that I could sort. But the sort takes place before the field is calculated so that didn't work.
I tried setting sections for each priority type - which worked. But it wasn't displaying in the right order and apparently you cannot order sections with core data.
Any other thoughts?
How about this?
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"priority" ascending:YES comparator:^NSComparisonResult(id obj1, id obj2) {
if ([obj1 isEqualToString:obj2]) {
return NSOrderedSame;
} else if ([obj1 isEqualToString:#"high"] || [obj2 isEqualToString:#"low"]) {
return NSOrderedAscending;
} else if ([obj2 isEqualToString:#"high"] || [obj1 isEqualToString:#"low"]) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];

NSComparator block not called

I am fetching data from Core Data storage using an NSFetchRequest and store that data in an array - all works great. As a next step, I want to sort that array using NSSortDescriptors like so:
array = [array sortedArrayUsingDescriptors:[NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"score" ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"score" ascending:NO comparator:^NSComparisonResult(id obj1, id obj2) {
if ([[[array objectAtIndex:[obj2 integerValue]] valueForKey:#"lessImportantItems"] containsObject:[array objectAtIndex:[obj1 integerValue]]]) {
return (NSComparisonResult)NSOrderedAscending;
} else {
return (NSComparisonResult)NSOrderedDescending;
}
}],
[NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO], nil]];
The problem I have is that the NSComparator block in the second NSSortDescriptor isn't called (I tried to NSLog). To give some background to my data structure, here's the relevant Core Data object graph section:
What the application does is it compares items with one another. As a first step, the winner in a paired comparison gets a score increment. But also I mark a paired priority, i.e. I add a one-to-many lessImportantItems relationship from the winner to the loser. So, in my array I first try to sort by score, and then, when scores are equal, I also try and sort by paired priority.
Maybe it's because I use score as comparator key twice in a row? But, on the other hand, NSComparator does not allow a relationship to be passed as a key either.
I can't seem to crack this one. Anyone has any ideas? Or perhaps I should take a different approach to sorting?
The second sort descriptor does not make any sense to me. It applies the given comparator
to the score attribute of the objects to compare. So inside the comparator,
obj1, obj2 are the score values of the to-be-compared objects.
It seems that you try to get the underlying objects with
[array objectAtIndex:[obj1 integerValue]]
[array objectAtIndex:[obj2 integerValue]]
but that cannot work. So the second sort descriptor should look like
[NSSortDescriptor sortDescriptorWithKey:#"self" ascending:NO
comparator:^NSComparisonResult(Item *item1, Item *item2) {
// compare item1, item2 ...
}];
But then the next problem arises: How to compare two objects according to priority?
Your code does essentially the following:
if ([item2 valueForKey:#"lessImportantItems"] containsObject:item1]) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
But that is not a proper comparator:
It does not return NSOrderedSame if the objects are equal (not "reflexive"),
For two "unrelated objects" it will return NSOrderedDescending regardless of the
order (not "asymmetric"),
it does not detect if item1 is only indirectly related to item2 (not
"transitive").
But how to sort "unrelated objects"? There is no unique solution. If both B and C are
less important than A, then both A, B, C and A, C, B are valid solutions.
What should the comparator return when comparing B and C?
So I think that cannot be achieved with a sort descriptor and
you have to choose some other algorithm, e.g. "Topological sorting".
If anyone's interested, here's how I achieved the sorting I was after.
I used only the first NSSortDescriptor from the example above to get the array sorted by score, and then I called a further sorting method on that array:
array = [array sortedArrayUsingDescriptors:[NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"score" ascending:NO], nil];
array = [self applyMoreImportantPairOrdering:array];
And here's the method:
+ (NSArray *)applyMoreImportantPairOrdering:(NSArray *)array {
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([[obj valueForKey:#"score"] integerValue] > 0) {
NSMutableSet *lessImportantItemsSet = [NSMutableSet setWithSet:[obj valueForKey:#"lessImportantItems"]];
for (int i = idx - 1; i >= 0; i--) {
NSManagedObject *objAbove = [array objectAtIndex:i];
if ([[obj valueForKey:#"score"] integerValue] == [[objAbove valueForKey:#"score"] integerValue]) {
if ([lessImportantItemsSet containsObject:objAbove]) {
NSUInteger idxAbove = [mutableArray indexOfObject:objAbove];
[mutableArray removeObject:obj];
[mutableArray insertObject:obj atIndex:idxAbove];
}
}
}
}
}];
return [NSArray arrayWithArray:mutableArray];
}
The reason I need the lessImportantItemsSet is because when I move (delete and insert) an item in an array, it loses its lessImportantItems relationships. This I way I maintain the list/set of less important items while I'm done with the particular item.

How to sort by either a distance in float or a price that is an int?

I am allowing users to be able to sort either by price (int) or by distance (float).
I have an NSMutableArray of NSdictionary objects that store the data as follows:
({"asking_price" = 588832;
distance = "2.0250673476224";
id = 510cc41cc7e24c6c6d000000;
"number_of_bathrooms" = 2; )}
my sort function is as follows:
+(void) sort:(NSMutableArray *)classifieds:(NSString *)key:(Boolean)isAscending
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:isAscending];
[classifieds sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
}
My question is considering distance is a string in the dictionary and price is an int, how can I modify my function to actually do sorting it in floats when I pass in key of "distance" and in ints when I pass in a key of "asking_price"
Thanks in advance
+(void) sort:(NSMutableArray *)classifieds:(NSString *)key:(Boolean)isAscending
{
NSSortDescriptor *sortDescriptor;
if ([key isEqualToString:#"distance"])
{
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:isAscending comparator:^NSComparisonResult(id obj1, id obj2) {
if ([obj1 floatValue] < [obj2 floatValue])
return NSOrderedAscending;
else
return NSOrderedDescending;
}];
}
else
{
sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:key ascending:isAscending];
}
[classifieds sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
}
This probably isn't possible as a function you don't have control of doesn't know how to convert an NSString to a primitive data type. You're pretty much left with 3 options:
1) Come up with your own sorting algorithm (not too hard with google)
2) Change distance to an int/float
3) Make a subclass of NSSortDescriptor and override the sorting method (pretty much the same outcome as the first choice except the code you use for this is reusable

NSSortDescriptor with arbitrary sorting

I can't wrap my head around how to do arbitrary sorting with a NSSortDescriptor.
I want to do something like this:
NSArray *sortAlgorithm = [NSArray arrayWithObjects:#"#", #"#", #"!", #"&", #"r", #"a", nil];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
comparator:
^(id obj1, id obj2) {
NSComparisonResult comparisonResult;
//Some code that uses sortAlgorithm.
return comparisonResult;
}
];
This would sort the objects by the key name so that any key that starts with #, e.g. #home, would come before any key that starts with r, e.g. radical, and that again would come before any key that starts with a, e.g. anything.
The above is just an example. The point is to enable completely arbitrary sorting.
This is to be used for a NSFetchedResultsController.
What would the code for //Some code that uses sortAlgorithm look like?
EDIT:
Code surrounding my attempt to implement a sortDescriptor, as pr. occulus' suggestion:
- (NSFetchedResultsController *)fetchedResultsController {
if (__fetchedResultsController)
return __fetchedResultsController;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Tag" inManagedObjectContext:self.temporaryManagedObjectContext];
//NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO comparator:^(id obj1, id obj2) {
NSArray *sortAlgorithm = [NSArray arrayWithObjects:#"#", #"!", #"#", #".", nil];
NSString *obj1FirstChar = [(NSString *)obj1 substringToIndex:1];
NSString *obj2FirstChar = [(NSString *)obj2 substringToIndex:1];
int idx1 = [sortAlgorithm indexOfObject:obj1FirstChar];
int idx2 = [sortAlgorithm indexOfObject:obj2FirstChar];
if ( idx1 < idx2 )
return NSOrderedAscending;
else if ( idx1 > idx2 )
return NSOrderedDescending;
else
return NSOrderedSame;
}];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
fetchRequest.fetchBatchSize = 20;
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.temporaryManagedObjectContext sectionNameKeyPath:nil cacheName:#"Tags"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[self.fetchedResultsController performFetch:nil];
return __fetchedResultsController;
}
The commented-out sortDescriptor works.
There is definitely a property called name on objects of entity "Tag". But even if there weren't, that doesn't seem to be the problem. Xcode doesn't seem to even be compiling that line of code (the sortDescriptor), which sounds ridiculous. Breakpoints are working just fine, but aren't breaking on that specific line of code.
You need to pull out the first characters of obj1 and obj2 strings as NSStrings, find their indexes in your arbitrary ordering array, then compare the positions.
Something like this: (put this code in your ^ block)
NSString *obj1FirstChar = [(NSString *)obj1 substringToIndex:1];
NSString *obj2FirstChar = [(NSString *)obj2 substringToIndex:1];
int idx1 = [sortAlgorithm indexOfObject:obj1FirstChar];
int idx2 = [sortAlgorithm indexOfObject:obj2FirstChar];
// NOTE: we haven't dealt with the case where one of the
// chars wasn't found in the array of characters. A strategy
// for this would need to be decided.
if (idx1 == idx2) {
return NSOrderedSame;
}
if (idx1 < idx2) {
return NSOrderedAscending;
}
return NSOrderedDescending;
Not tested, may require a little tweaking. Watch out for upper/lower case character differences.
UPDATE: It seems there are problems with using custom sort descriptors and SQL backed core data stores. Please see this question for more info.

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

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?
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.
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.
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.
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]];
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.
There is a missing step in Georg Schölly's second answer, but it works fine then.
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"birthDate"
ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];
// added the 's' because time was wasted when I copied and pasted and it failed without the 's' in sortedArrayUsingDescriptors
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"birthDate" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];
Thanks, it's working fine...
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.
Then 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.
iOS 4 blocks will save you :)
featuresArray = [[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
For NSMutableArray, use the sortUsingSelector method. It sorts it-place, without creating a new instance.
You can use the following generic method for your purpose. It should solve your issue.
//Called method
-(NSMutableArray*)sortArrayList:(NSMutableArray*)arrDeviceList filterKeyName:(NSString*)sortKeyName ascending:(BOOL)isAscending{
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:sortKeyName ascending:isAscending];
[arrDeviceList sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
return arrDeviceList;
}
//Calling method
[self sortArrayList:arrSomeList filterKeyName:#"anything like date,name etc" ascending:YES];
If you're just sorting an array of NSNumbers, you can sort them with 1 call:
[arrayToSort sortUsingSelector: #selector(compare:)];
That works because the objects in the array (NSNumber objects) implement the compare method. You could do the same thing for NSString objects, or even for an array of custom data objects that implement a compare method.
Here's some example code using comparator blocks. It sorts an array of dictionaries where each dictionary includes a number in a key "sort_key".
#define SORT_KEY #\"sort_key\"
[anArray sortUsingComparator:
^(id obj1, id obj2)
{
NSInteger value1 = [[obj1 objectForKey: SORT_KEY] intValue];
NSInteger value2 = [[obj2 objectForKey: SORT_KEY] intValue];
if (value1 > value2)
{
return (NSComparisonResult)NSOrderedDescending;
}
if (value1 < value2)
{
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
The code above goes through the work of getting an integer value for each sort key and comparing them, as an illustration of how to do it. Since NSNumber objects implement a compare method, it could be rewritten much more simply:
#define SORT_KEY #\"sort_key\"
[anArray sortUsingComparator:
^(id obj1, id obj2)
{
NSNumber* key1 = [obj1 objectForKey: SORT_KEY];
NSNumber* key2 = [obj2 objectForKey: SORT_KEY];
return [key1 compare: key2];
}];
or the body of the comparator could even be distilled down to 1 line:
return [[obj1 objectForKey: SORT_KEY] compare: [obj2 objectForKey: SORT_KEY]];
I tend to prefer simple statements and lots of temporary variables because the code is easier to read, and easier to debug. The compiler optimizes away the temporary variables anyway, so there is no advantage to the all-in-one-line version.
You use NSSortDescriptor to sort an NSMutableArray with custom objects
NSSortDescriptor *sortingDescriptor;
sortingDescriptor = [[NSSortDescriptor alloc] initWithKey:#"birthDate"
ascending:YES];
NSArray *sortArray = [drinkDetails sortedArrayUsingDescriptors:#[sortDescriptor]];
-(NSMutableArray*) sortArray:(NSMutableArray *)toBeSorted
{
NSArray *sortedArray;
sortedArray = [toBeSorted sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
{
return [a compare:b];
}];
return [sortedArray mutableCopy];
}
I have created a small library of category methods, called Linq to ObjectiveC, that makes this sort of thing more easy. Using the sort method with a key selector, you can sort by birthDate as follows:
NSArray* sortedByBirthDate = [input sort:^id(id person) {
return [person birthDate];
}]
I just done multi level sorting based on custom requirement.
//sort the values
[arrItem sortUsingComparator:^NSComparisonResult (id a, id b){
ItemDetail * itemA = (ItemDetail*)a;
ItemDetail* itemB =(ItemDetail*)b;
//item price are same
if (itemA.m_price.m_selling== itemB.m_price.m_selling) {
NSComparisonResult result= [itemA.m_itemName compare:itemB.m_itemName];
//if item names are same, then monogramminginfo has to come before the non monograme item
if (result==NSOrderedSame) {
if (itemA.m_monogrammingInfo) {
return NSOrderedAscending;
}else{
return NSOrderedDescending;
}
}
return result;
}
//asscending order
return itemA.m_price.m_selling > itemB.m_price.m_selling;
}];
https://sites.google.com/site/greateindiaclub/mobil-apps/ios/multilevelsortinginiosobjectivec
I've used sortUsingFunction:: in some of my projects:
int 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:
NSMutableArray *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];
Sort using NSComparator
If we want to sort custom objects we need to provide NSComparator, which is used to compare custom objects. The block returns an NSComparisonResult value to denote the ordering of the two objects. So in order to sort whole array NSComparator is used in following way.
NSArray *sortedArray = [employeesArray sortedArrayUsingComparator:^NSComparisonResult(Employee *e1, Employee *e2){
return [e1.firstname compare:e2.firstname];
}];
Sorts Using NSSortDescriptor
Let’s assume, as an example, that we have an array containing instances of a custom class, Employee has attributes firstname, lastname and age. The following example illustrates how to create an NSSortDescriptor that can be used to sort the array contents in ascending order by the age key.
NSSortDescriptor *ageDescriptor = [[NSSortDescriptor alloc] initWithKey:#"age" ascending:YES];
NSArray *sortDescriptors = #[ageDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];
Sort using Custom Comparisons
Names are strings, and when you sort strings to present to the user you should always use a localized comparison. Often you also want to perform a case insensitive comparison. Here comes an example with (localizedStandardCompare:) to order the array by last and first name.
NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"lastName" ascending:YES selector:#selector(localizedStandardCompare:)];
NSSortDescriptor * firstNameDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"firstName" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortDescriptors = #[lastNameDescriptor, firstNameDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];
For reference and detailed discussion please refer:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/SortDescriptors/Articles/Creating.html
http://www.ios-blog.co.uk/tutorials/objective-c/how-to-sort-nsarray-with-custom-objects/
Swift's protocols and functional programming makes that very easy you just have to make your class conform to the Comparable protocol, implement the methods required by the protocol and then use the sorted(by: ) high order function to create a sorted array without need to use mutable arrays by the way.
class Person: Comparable {
var birthDate: NSDate?
let name: String
init(name: String) {
self.name = name
}
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.birthDate === rhs.birthDate || lhs.birthDate?.compare(rhs.birthDate as! Date) == .orderedSame
}
static func <(lhs: Person, rhs: Person) -> Bool {
return lhs.birthDate?.compare(rhs.birthDate as! Date) == .orderedAscending
}
static func >(lhs: Person, rhs: Person) -> Bool {
return lhs.birthDate?.compare(rhs.birthDate as! Date) == .orderedDescending
}
}
let p1 = Person(name: "Sasha")
p1.birthDate = NSDate()
let p2 = Person(name: "James")
p2.birthDate = NSDate()//he is older by miliseconds
if p1 == p2 {
print("they are the same") //they are not
}
let persons = [p1, p2]
//sort the array based on who is older
let sortedPersons = persons.sorted(by: {$0 > $1})
//print sasha which is p1
print(persons.first?.name)
//print James which is the "older"
print(sortedPersons.first?.name)
In my case, I use "sortedArrayUsingComparator" to sort an array. Look at the below code.
contactArray = [[NSArray arrayWithArray:[contactSet allObjects]] sortedArrayUsingComparator:^NSComparisonResult(ContactListData *obj1, ContactListData *obj2) {
NSString *obj1Str = [NSString stringWithFormat:#"%# %#",obj1.contactName,obj1.contactSurname];
NSString *obj2Str = [NSString stringWithFormat:#"%# %#",obj2.contactName,obj2.contactSurname];
return [obj1Str compare:obj2Str];
}];
Also my object is,
#interface ContactListData : JsonData
#property(nonatomic,strong) NSString * contactName;
#property(nonatomic,strong) NSString * contactSurname;
#property(nonatomic,strong) NSString * contactPhoneNumber;
#property(nonatomic) BOOL isSelected;
#end
You have to create sortDescriptor and then you can sort the nsmutablearray by using sortDescriptor like below.
let sortDescriptor = NSSortDescriptor(key: "birthDate", ascending: true, selector: #selector(NSString.compare(_:)))
let array = NSMutableArray(array: self.aryExist.sortedArray(using: [sortDescriptor]))
print(array)
Sort Array In Swift
For Swifty Person below is a very clean technique to achieve above goal for globally. Lets have an example custom class of User which have some attributes.
class User: NSObject {
var id: String?
var name: String?
var email: String?
var createdDate: Date?
}
Now we have an array which we need to sort on the basis of createdDate either ascending and/or descending. So lets add a function for date comparison.
class User: NSObject {
var id: String?
var name: String?
var email: String?
var createdDate: Date?
func checkForOrder(_ otherUser: User, _ order: ComparisonResult) -> Bool {
if let myCreatedDate = self.createdDate, let othersCreatedDate = otherUser.createdDate {
//This line will compare both date with the order that has been passed.
return myCreatedDate.compare(othersCreatedDate) == order
}
return false
}
}
Now lets have an extension of Array for User. In simple words lets add some methods only for those Array's which only have User objects in it.
extension Array where Element: User {
//This method only takes an order type. i.e ComparisonResult.orderedAscending
func sortUserByDate(_ order: ComparisonResult) -> [User] {
let sortedArray = self.sorted { (user1, user2) -> Bool in
return user1.checkForOrder(user2, order)
}
return sortedArray
}
}
Usage for Ascending Order
let sortedArray = someArray.sortUserByDate(.orderedAscending)
Usage for Descending Order
let sortedArray = someArray.sortUserByDate(.orderedAscending)
Usage for Same Order
let sortedArray = someArray.sortUserByDate(.orderedSame)
Above method in extension will only be accessible if the Array is of type
[User] || Array<User>
Use like this for nested objects,
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastRoute.to.lastname" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSMutableArray *sortedPackages = [[NSMutableArray alloc]initWithArray:[packages sortedArrayUsingDescriptors:#[sortDescriptor]]];
lastRoute is one object and that object holds the to object, that to object hold the lastname string values.
Swift version: 5.1
If you have a custom struct or class and want to sort them arbitrarily, you should call sort() using a trailing closure that sorts on a field you specify. Here's an example using an array of custom structs that sorts on a particular property:
struct User {
var firstName: String
}
var users = [
User(firstName: "Jemima"),
User(firstName: "Peter"),
User(firstName: "David"),
User(firstName: "Kelly"),
User(firstName: "Isabella")
]
users.sort {
$0.firstName < $1.firstName
}
If you want to return a sorted array rather than sort it in place, use sorted() like this:
let sortedUsers = users.sorted {
$0.firstName < $1.firstName
}
let sortedUsers = users.sorted {
$0.firstName < $1.firstName
}
NSMutableArray *stockHoldingCompanies = [NSMutableArray arrayWithObjects:fortune1stock,fortune2stock,fortune3stock,fortune4stock,fortune5stock,fortune6stock , nil];
NSSortDescriptor *sortOrder = [NSSortDescriptor sortDescriptorWithKey:#"companyName" ascending:NO];
[stockHoldingCompanies sortUsingDescriptors:[NSArray arrayWithObject:sortOrder]];
NSEnumerator *enumerator = [stockHoldingCompanies objectEnumerator];
ForeignStockHolding *stockHoldingCompany;
NSLog(#"Fortune 6 companies sorted by Company Name");
while (stockHoldingCompany = [enumerator nextObject]) {
NSLog(#"===============================");
NSLog(#"CompanyName:%#",stockHoldingCompany.companyName);
NSLog(#"Purchase Share Price:%.2f",stockHoldingCompany.purchaseSharePrice);
NSLog(#"Current Share Price: %.2f",stockHoldingCompany.currentSharePrice);
NSLog(#"Number of Shares: %i",stockHoldingCompany.numberOfShares);
NSLog(#"Cost in Dollars: %.2f",[stockHoldingCompany costInDollars]);
NSLog(#"Value in Dollars : %.2f",[stockHoldingCompany valueInDollars]);
}
NSLog(#"===============================");

Resources