Search code examples
objective-ccocoanstableviewnsarraycontrollernsbutton

Set NSButton enabled based on NSArrayController selection


OK, so I've set up an NSTableView bound to NSArrayController.

Now, I also have an NSButton which I want to make it "enabled" when there is a selection, and disabled when there is nothing selected.

So, I'm bind NSButton's Enabled to the Array Controller's selection with a value transformer of NSIsNotNil.

NSIsNotNil Array Controller and NSButton

However, it doesn't seem to be working.

Am I missing anything?


Solution

  • Regardless of whether or not anything is selected, the selection property of NSArrayController returns an object (_NSControllerProxyObject). This is why your binding isn't working the way you expect, because selection will never be nil. Instead, I bind to selectionIndexes, rather than selection, and have a value transformer called SelectionIndexesCountIsZero implemented like so:

    @interface SelectionIndexesCountIsZero : NSValueTransformer
    @end
    
    @implementation SelectionIndexesCountIsZero
    
    + (Class)transformedValueClass { return [NSNumber class]; }
    
    + (BOOL)allowsReverseTransformation { return NO; }
    
    - (id)transformedValue:(NSIndexSet *)value { return [NSNumber numberWithBool:[value count] > 0]; }
    
    @end
    

    Incidentally, you can still bind to selection if you wish, but it will require a custom value transformer. Apple state that: If a value requested from the selection proxy [object] using key-value coding returns multiple objects, the controller has no selection, or the proxy is not key-value coding compliant for the requested key, the appropriate marker is returned. In other words, to find out if there is in fact no selection, you need to (i) get access to the proxy object, (ii) call one of the methods of your actual objects, and (iii) test to see if the return value from (ii) is NSNoSelectionMarker. Doing it this way the key method of your value transformer would look like this:

    - (id)transformedValue:(id)selectionProxyObject {
        // Assume the objects in my table are Team objects, with a 'name' property
        return [selectionProxyObject valueForKeyPath:@"name"] == NSNoSelectionMarker ? @YES : @NO;
    }
    

    selectionIndexes is the better way since it is completely generic. In fact, if you do this sort of thing a lot it can be a good idea to build up a transformer library, which you can then just import into any project. Here are the names of some of the transformers in my library:

    SelectionIndexesCountIsZero
    SelectionIndexesCountIsExactlyOne
    SelectionIndexesCountIsOneOrGreater
    SelectionIndexesCountIsGreaterThanOne
    // ...you get the picture