views:

188

answers:

2

I'm trying to limit the number of objects in an array controller, but I still want to be able to access the full array, if necessary. A simple solution I came up with was to subclass NSArrayController, and define a new method named "limitedArrangedObjects", that returns a limited number of objects from the real set of arranged objects. (I've seen http://stackoverflow.com/questions/694493/limiting-the-number-of-objects-in-nsarraycontroller , but that doesn't address my problem.)

I want this property to be observable via bindings, so I set a dependency to arrangedObjects on it.

Problem is, when arrangedObjects is updated, limitedArrangedObjects seems not to be observing the value change in arrangedObjects. I've hooked up an NSCollectionView to limitedArrangedObjects, and zero objects are being displayed. (If I bind it to arrangedObjects instead, all the objects show up as expected.)

What's the problem?

Here's the relevant code:

@property (readonly) NSArray *limitedArrangedObjects;

- (NSArray *)limitedArrangedObjects;
{
    NSArray *arrangedObjects = [super arrangedObjects];

    NSUInteger upperLimit = 10000;

    NSUInteger count = [arrangedObjects count];
    if (count > upperLimit) count = upperLimit;
    arrayToReturn = [arrangedObjects subarrayWithRange:NSMakeRange(0, count)];


    return arrayToReturn;
}


+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key;
{
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"limitedArrangedObjects"]) {
        NSSet *affectingKeys = [NSSet setWithObjects:@"arrangedObjects",nil];
        keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKeys];
    }

    return keyPaths;
}
+2  A: 

I would probably take a different approach, which would be to override -arrangeObjects:, which can filter out objects and then set an auxiliary property like completeArrangedObjects to let you access the whole array. It'd look something like this:

- (NSArray*)arrangeObjects:(NSArray*)originalObjects
{
    NSArray* arrayToReturn;
    NSUInteger upperLimit = 10000;

    NSUInteger count = [originalObjects count];
    if (count > upperLimit) count = upperLimit;
    arrayToReturn = [arrangedObjects subarrayWithRange:NSMakeRange(0, count)];
    [self setCompleteArrangedObjects:originalObjects];
    return arrayToReturn;
}

Then you'd just bind your NSCollectionView to arrangedObjects, and since you're using a setter for completeArrangedObjects, that should also trigger KVO notifications when appropriate if you want to bind to it as well.

Brian Webster
This could potentially be an elegant solution, but it doesn't seem to work if I need to bind on sub-properties of completeArrangedObjects. For example, I have a property of the objects in the NSArrayController called "name", and I can bind to that in a separate table view.If I override arrangeObjects: and create a new completeArrangedObjects property, I cannot bind to the "name" subproperty, as it throws an exception:[<NSCFArray 0x10360f730> addObserver:forKeyPath:options:context:] is not supported. Key path: artist
Simone Manganelli
A: 

I can't remember where/who told me this, but I was informed that observing the "arrangedObjects" key using keyPathsForValuesAffectingValueForKey: currently doesn't work for NSArrayControllers. You can observe sub-keys (arrangedObjects.name, etc.), but you can't observe arrangedObjects directly.

So, the solution in my case was to create two instantiated objects in my nib file, a normal NSArrayController and an AFArrayController. AFArrayController has arrangedObjects: defined as such:

- (NSArray *)arrangedObjects;
{
    return [self limitedArrayFromArray:[super arrangedObjects]];
}

- (NSArray *)limitedArrayFromArray:(NSArray *)theArray;
{
    NSUInteger upperLimit = 0;
    if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_5) {
        upperLimit = 500;
    } else {
        upperLimit = 10000;
    }

    NSUInteger count = [theArray count];
    if (count > upperLimit) count = upperLimit;
    NSArray *arrayToReturn = [theArray objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, count)]];


    return arrayToReturn;
}

Now, I can observe arrangedObjects on NSArrayController if I want to get all the objects, or arrangedObjects on the AFArrayController if I want to get the limited set, and I can observe sub-properties on both the NSArrayController and the AFArrayController.

Simone Manganelli