What I want to accomplish seems like it should be fairly straightforward. I have placed a sample project here.
I have a NSArrayController filled with an array of NSDictionaries.
[[self controller] addObject:@{ @"name" : @"itemA", @"part" : @"partA" }];
[[self controller] addObject:@{ @"name" : @"itemB", @"part" : @"partB" }];
[[self controller] addObject:@{ @"name" : @"itemC", @"part" : @"partC" }];
I am populating a NSPopupButton with the items in this array based on the 'name' key. This is easily accomplished with the following bindings
I would then like to populate a NSTextField with the text in the 'part' key based on the current selection of the NSPopupButton. I have setup the following binding:
With these bindings alone, the text field does display 'partC'.
However, if I change the value of the NSPopupMenu, what the text field shows does not change.
I thought this would simply be a matter of setting up the 'Selected Object' binding on the NSPopupButton
but that isn't working. I end up with the proxy object in my menu for some strange reason (providing the reason why would be a bonus).
So, what do I need to do to make this work?
Don't use "Selected Object" in this case. Bind the pop-up's "Selected Index" binding to the NSArrayController's selectionIndex
Controller Key. Tried it out on your sample project and it works.
EDIT:
You asked why it's appropriate to use selectionIndex
over selectedObject
. First some background:
When binding a popup menu, there are three virtual "Collections" you can bind: Content is the abstract "list of things that should be in the menu" -- you must always specify Content. If you specify neither Content Objects, nor Content Values, then the collection of values bound to Content will be used as the "objects" and the strings returned by their -description
methods will be used as the "values". In other words, the Content Values are the strings displayed in the pop-up and the Content Objects are the things they correspond to (which are possibly not strings, and which might not have a -description
method suitable for generating the text in the pop-up). What's important to realize here is that there are potentially three different 'virtual arrays' in play here: The array for Content, the array for Content Objects (which may be different) and the array for Content Values (which may also be different). They will all have the same number of values, and typically, the Content Objects and Content Values will be functions (in the mathematical sense) of the corresponding items in the Content array.
The next thing that's important to realize is that part of NSArrayController
's purpose in life is to keep track of the user's selection. This is only mildly (if at all) interesting in the case of a pop-up, but starts to become far more interesting in the case of an NSTableView
. Internally, NSArrayController
keeps track of this by keeping an NSIndexSet
containing the indexes in the Content array that are selected at any given time. From there, selection state is exposed in several different ways for your convenience:
selectionIndexes
is as described - an NSIndexSet
containing the indexes of the selected items in the Content arrayselectionIndex
is a convenient option for applications that do not support multiple selection. It can be thought of as being equivalent to arrayController.selectionIndexes.firstIndex
.selectedObject
is also useful in single selection cases, and corresponds conceptually to ContentObjectsArray[arrayController.selectionIndexes.firstIndex]
selection
returns a special object (opaque to the consumer) that brokers reads and writes back to the underlying object (or objects in the case of multiple selection) in the Content Array of the array controller. It exists to enable editing multiple objects at a time in multiple selection cases, and to provide support for other special cases. (You should think of this property as read-only; Since its type is opaque to the consumer, you could never make a suitable new value to write into it. It's meaningful to make calls like: -[arrayController.selection setValue: myObject forKey: @"modelKey"]
, but it's not meaningful to make calls like -[arrayController setValue: myObject forKey: @"selection"]
With that understanding of the selection
property, let's take a step back and see why it's not the right thing to use in this case. NSPopUpButton
tries to be smart: You've provided it with a list of things that should be in the menu via the Content and Content Values bindings. Then you've additionally told it that you want to bind its Selected Object to the the NSArrayController
's selection
property. You're probably thinking of this as a "write only" binding -- i.e. "Dear pop-up, please take the user's selection and push it into the arrayController", but the binding is really bi-directional. So when the bindings refresh, the popup first populates the menu with all the items from the Content/Content Values bindings, and then it says, "Oh, you say the value at arrayController.selection
is my Selected Object. That's odd -- it's not in the list of things bound with my Content/Content Values bindings. I'd better add it to the list for you! I'll do that by calling -description
on it, and plunking that string into the menu for you." But the object you get from that Selected Object binding is the opaque selection object described above (and you can see from the outcome that it is of class _NSControllerObjectProxy
, a private-to-AppKit class as hinted by the leading underscore).
In sum, that's why binding your popup's Selected Object binding to the array controller's selection
controller key is the wrong thing to do here. Sad to say, but as I'm sure you've discovered, the documentation for Cocoa bindings only begins to scratch the surface, so don't feel bad. I've been working with Cocoa bindings pretty much daily, in a large-scale project, for several years now, and I still feel like there are a lot of use cases I don't yet fully understand.