3

I'm using NSSortDescriptor to sort an NSArray of NSDictionary items. Works great, just what I needed... except that I'd like for the dictionary items that have blank values for my sort key to show up at the end of the sorted list. Is there a way to easily accomplish this? Or will I have to create some custom sorting functions? And no, I don't want to just set it to DESC order... I'd like sorted results to look like A, A, B, B, C, blanks, blank.

Costique
  • 23,712
  • 4
  • 76
  • 79
Bryan
  • 8,748
  • 7
  • 41
  • 62

3 Answers3

3

Figured this out after seeing another example using a custom comparison. Here is the code I ended up with:

@interface NSString (CustomStatusCompare)
- (NSComparisonResult)customStatusCompare:(NSString*)other;
@end

@implementation NSString (CustomStatusCompare)
- (NSComparisonResult)customStatusCompare:(NSString*)other {
    NSAssert([other isKindOfClass:[NSString class]], @"Must be a NSString");
    if ([self isEqual:other]) {
        return NSOrderedSame;
    }
    else if ([self length] > 0 && [other length] > 0) {
        return [self localizedCaseInsensitiveCompare:other];        
    }
    else if ([self length] > 0 && [other length] == 0) {
        return NSOrderedAscending;
    }
    else {
        return NSOrderedDescending;
    } 
}
@end



NSSortDescriptor *serviceTypeDescriptor =
[[NSSortDescriptor alloc] initWithKey:@"Service"
                             ascending:YES
                             selector:@selector(localizedCaseInsensitiveCompare:)];

NSSortDescriptor *locationDescriptor = 
[[NSSortDescriptor alloc] initWithKey:@"Location"
                             ascending:YES
                             selector:@selector(customStatusCompare:)];  //using custom comparison here!

NSArray *descriptors = [NSArray arrayWithObjects:locationDescriptor, nameDescriptor, nil];        
self.navArray = [self.navArray sortedArrayUsingDescriptors:descriptors];

So the comparer returns NSOrderedSame if both strings are empty... calls the regular comparison function if both strings are non-empty... if only one string is empty, it reverses the normal order of that comparison. Voila!

Bryan
  • 8,748
  • 7
  • 41
  • 62
  • Wow, sorry, I somehow totally misread your question and thought you wanted strings with leading blanks first :/ I'll go work on my reading comprehension... – Doug McBride Mar 19 '12 at 20:08
  • Note that this won't work within a compile predicate in core data :/ – deepwinter Apr 11 '18 at 22:49
0

Do the Sort using NSNumericSort instead of NSDiacriticInsensitive or NSCaseInsensitive. Since the whitespace is probably 256 in ascii code it will be sent to the back of the list.

Panagiotis
  • 1,539
  • 1
  • 14
  • 28
0

Edit: I misread the question and thought the intent was to put strings with leading spaces first in sort order (which they should anyway).

You want something like this, I think. This is completely untested and doesn't take multiple leading spaces into account. I'm sure there's a more elegant way, but it should get the job done.

NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
    NSString *str1 = (NSString *)obj1;
    NSString *str2 = (NSString *)obj2;

    BOOL str1HasSpace = [str1 hasPrefix:@" "];
    BOOL str2HasSpace = [str2 hasPrefix:@" "];

    if (str1HasSpace && !str2HasSpace) {
        return NSOrderedAscending;
    } else if (str2HasSpace && ! str1HasSpace) {
        return NSOrderedDescending;
    } else {
        return [[str1 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] compare:[str2 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
    }
}];
Doug McBride
  • 514
  • 1
  • 4
  • 13
  • Thanks, I see where you're going here. What I ended up doing was very similar... putting this custom comparison inside its own method in an NSString category (is that the right name?) instead of a block. – Bryan Mar 16 '12 at 20:55