NSSortDescriptor: Custom comparison on multiple keys simultaneously

AlexR picture AlexR · Oct 16, 2012 · Viewed 10.7k times · Source

I have an custom object which contains time period based information, for instance the attributes endCalYear, endMonth and periodLength which indicate the end of each period and its length.

I would like to create an NSSortDescriptor based or other sorting method which combines these three attributes and allows sorting this object on all three keys at the same time.

Example:

EndCalYear  endMonth  periodLength (months) sortOrder
2012        6         6                     1
2012        6         3                     2
2012        3         3                     3
2011        12        12                    4

The sort algorithm would be completely discretionary based on my own algorithm.

How could I code such an algorithm?

The block based sortDescriptorWithKey:ascending:comparator: method won't work in my view because it would allow me to specify only one sorting key. However, I need to sort on all three keys at the same time.

Any ideas or thoughts on how to solve this?

Thank you!

Answer

Nathan Villaescusa picture Nathan Villaescusa · Oct 16, 2012

You could sort with a block instead:

NSArray *sortedArray;
sortedArray = [myArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    MyObject *first = (MyObject*)a;
    MyObject *second = (MyObject*)b;

    if (first.endCalYear < second.endCalYear) {
        return NSOrderedAscending;
    }
    else if (first.endCalYear > second.endCalYear) {
        return NSOrderedDescending;
    }
    // endCalYear is the same

    if (first.endMonth < second.endMonth) {
        return NSOrderedAscending;
    }
    else if (first.endMonth > second.endMonth) {
        return NSOrderedDescending;
    }    
    // endMonth is the same

    if (first.periodLength < second.periodLength) {
        return NSOrderedAscending;
    }
    else if (first.periodLength > second.periodLength) {
        return NSOrderedDescending;
    }
    // periodLength is the same

    return NSOrderedSame;
}]

This sorts by endCalYear ascending, then endMonth ascending and finally periodLength ascending. You could modify it to change the order or switch the signs in the if statement to make it descending.

For NSFetchedResultsController you might want to try something else:

It looks like you can pass it a list of descriptors, one for each column that you want sorted:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSSortDescriptor *descriptor1 = [[NSSortDescriptor alloc] initWithKey:@"endCalYear" ascending:YES];
NSSortDescriptor *descriptor2 = [[NSSortDescriptor alloc] initWithKey:@"endMonth" ascending:YES];
NSSortDescriptor *descriptor3 = [[NSSortDescriptor alloc] initWithKey:@"periodLength" ascending:YES];
NSArray *sortDescriptors = @[descriptor1, descriptor2, descriptor3];
[fetchRequest setSortDescriptors:sortDescriptors];