Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Controlling the way NSSortDescriptor sorts nil values in Core Data

Given the following NSSortDescriptor for strings with Core Data:

[NSSortDescriptor sortDescriptorWithKey:@"series" ascending:true selector:@selector(caseInsensitiveCompare:)]

The results are correctly ordered alphabetically ascending. However in instances where series is nil, strings with nil values are placed at the top, with non-nil values being sorted thereafter, E.G:

[nil, nil, nil, A, B, C, D...]

Is there any way to control this behavior? Core Data does not allow for custom selectors. Here's a similar question to mine (not addressing Core Data's limitation, however):

NSSortDescriptor and nil values

like image 968
mattsven Avatar asked Sep 06 '14 14:09

mattsven


2 Answers

While you cannot use a custom selector with Core Data, you can subclass NSSortDescriptor to change the default behavior. Something like this should work:

#define NULL_OBJECT(a) ((a) == nil || [(a) isEqual:[NSNull null]])

@interface NilsLastSortDescriptor : NSSortDescriptor {}
@end

@implementation NilsLastSortDescriptor

- (id)copyWithZone:(NSZone*)zone
{
    return [[[self class] alloc] initWithKey:[self key] 
                           ascending:[self ascending] selector:[self selector]];
}

- (id)reversedSortDescriptor
{
    return [[[self class] alloc] initWithKey:[self key] 
                           ascending:![self ascending] selector:[self selector]];
}

- (NSComparisonResult)compareObject:(id)object1 toObject:(id)object2 
{
    if (NULL_OBJECT([object1 valueForKeyPath:[self key]]) && 
        NULL_OBJECT([object2 valueForKeyPath:[self key]]))
        return NSOrderedSame;

    if (NULL_OBJECT([object1 valueForKeyPath:[self key]]))
        return NSOrderedDescending;

    if (NULL_OBJECT([object2 valueForKeyPath:[self key]]))
        return NSOrderedAscending;

    return [super compareObject:object1 toObject:object2];
}

@end
like image 140
memmons Avatar answered Nov 19 '22 18:11

memmons


Swift 4.1 version:

class NilsLastSortDescriptor: NSSortDescriptor {

    override func copy(with zone: NSZone? = nil) -> Any {
        return NilsLastSortDescriptor(key: self.key, ascending: self.ascending, selector: self.selector)
    }

    override var reversedSortDescriptor: Any {
        return NilsLastSortDescriptor(key: self.key, ascending: !self.ascending, selector: self.selector)
    }

    override func compare(_ object1: Any, to object2: Any) -> ComparisonResult {


        if (object1 as AnyObject).value(forKey: self.key!) == nil && (object2 as AnyObject).value(forKey: self.key!) == nil {
            return .orderedSame
        }

        if (object1 as AnyObject).value(forKey: self.key!) == nil {
            return .orderedDescending
        }
        if (object2 as AnyObject).value(forKey: self.key!) == nil {
            return .orderedAscending
        }

        return super.compare(object1, to: object2)
    }
}
like image 7
netbe Avatar answered Nov 19 '22 18:11

netbe