Binding an NSPopupButton to an NSDictionaryController - objective-c

I'm trying out some MacOS programming and having some trouble understanding how bindings work with an NSPopupButton. I'm interested in binding to an NSDictionaryController (I don't think I need an intermediate NSArrayController but if that is the best way, I'm open to it).
I've created a controller object that has a property 'db' which has a property 'species' which is an NSMutableDictionary. The 'species' dictionary has ID's for keys and Species objects for values. Species objects have a description property. In InterfaceBuilder, I've created MyController, NSDictionaryController, and an NSPopupButton. I would like to populate the popup with Species.descriptions. When selected, I need access to the corresponding ID.
I've setup the NSDictionaryController to bind 'Content Dictionary' to MyController with Model Key Path 'db.species'. With NSPopupButton, so far I've bound 'Content Values' to NSDictionaryController with controller key 'arrangedObjects' and Model Key Path set to 'value.description'.
This seems to work getting the list populated. My main question is what the best way to wire up the selection is. Ideally, I would like to wire selection to the NSDictionaryController so that I can use the NSDictionaryController to access the selection. One reason for this is so that I can wire other controls to the NSDictionaryController to see the current selection. If not, should I wire to a property in MyController or something? Just looking for the best practices. I would like as much to be through the Interface Builder mechanisms so that I can easily reuse the model and controller design in another application with a different view.
Update using Brian's answer as guidance:
NSPopupButton:
bind Content to NSDictionaryController->arrangedObjects->value.description
bind Content Objects to NSDictionaryController->arrangedObjects->key
bind Selected Index to NSDictionaryController->selectionIndex
bind NSDictionaryController->db.species
Everything seems to work. I can grab the object from the controller with [[[controller selectedObjects] lastObject] value]. It's in an array of selected objects with key, value pairs, I believe.

I've never tried this with an NSDictionaryController, but I think you would want to bind the contentObjects of the pop-up to the dict controller's "arrangedObjects.key" and the selectedObject binding to the dict controller's "selection" key. The contentObjects binding would specify the IDs as being the underlying objects represented by each menu item. Then when an item is selected from the pop-up, the selectedObject binding would set the ID corresponding to that menu item as the selection of the dict controller.

I would like to populate the popup with Species.descriptions. When selected, I need access to the corresponding ID.
Bind content to the dictionary controller's arrangedObjects.value (don't include description—the pop-up button will do that for you) and contentObjects to the dictionary controller's arrangedObjects.key.
For more info, see NSPopUpButton in the Cocoa Bindings Reference.
(I notice it describes content as “An NSArrayController instance …”. Dictionary controllers are array controllers, so that shouldn't be a problem, but binding to a property of the controller may be. Something to watch out for.)

Related

Is it possible to bind a `NSTextfield` to only one particular entry in a NSArray or NSSet/ Relationship

Is it possible to bind a NSTextfield to only one particular entry in a NSArray or NSSet/ Relationship.
I can see the possibility of binding to an NSArrayController using the Control Key of filterPredicate but what would be the Model Key Path?
Further, can a single NSArrayController have many filterPredicates either methods or properties.
As far as I know, you cannot bind to a specific object in a set because there is no way to consistently express a given object within the set. The only method for extracting an object from a set is anyObject. For arrays its another matter. They can be indexed them and the bindings API allows you to do this:
// Edit: changed the code to use bindings directly instead of KVO
[_textFild bind:NSValueBinding
toObject:array[indexToBindTo]
withKeyPath:#"firstName"
options:nil];
You can't do this directly in Interface Builder so it has to be done in code.

Can I use an NSDictionaryController for a dictionary of dictionaries?

I have a plist file that holds information I need to display in an app organised as a dictionary of dictionaries. I've just started programming Cocoa so am not sure the best way to go about this. Obviously I can do it all manually, and code up the loops and add the data to the UI elements, but it seems to me that bindings and the provided controllers should let me do this more easily.
I was specifically wondering if there was a direct way (e.g. using mostly Interface Builder) to link the NSDictionary I get from reading the plist file, that itself contains further NSDictionary elements, which in turn contain name-value string pairs, to an appropriate user interface element -- probably an outline view or a browser.
Alternatively, the data would fit into a function browser type panel (like in Excel) where the top level keys are categories of functions, the next level are functions in that category, and I can just populate a text area with the lowest-level details -- i.e. the value data from the final dictionary.
I don't think you are going to be able to do this with an NSBrowser or NSOutlineView. The reason I say that is because if you are using bindings with those views you need to use an NSTreeController. NSTreeController provides the ability to specify which keys in your model indicate whether or not the current object has children objects (isLeaf) and how to access the children objects (children).
So if you are going to use one of those two views, you must be able to add additional keys and properties to your model to do so. Many times when I work with NSOutlineView and NSBrowser I find it easiest to skip bindings altogether and just use the all delegate & datasource methods. They require more code but they aren't hard to put together and sometimes I prefer them to bindings if my data model is complex or if the data is not in a format that is easily pumped through an NSTreeController.
However you could use an NSTableView by doing the following.
Create an NSDictionaryController in your NIB.
In the controller that reads in your plist, create an outlet for the NSDictionaryController and hookup the outlet using Interface Builder.
In the code that reads the plist, add an additional line of code that set's the NSDictionaryController's content to the root dictionary from the plist.
In your NIB, create an NSArrayController. Bind the array controller's "Content Array" binding to the NSDictionaryController. For the "Controller Key" binding property, specify "arrangedObjects".
Now take an NSTableView and place it in your NIB. Bind each of the NSTableColumn's "Value" bindings to the NSArrayController and for the "Controller Key" binding property, specify the key from the dictionary whose value you want to display in the table column.

What do you need to implement to provide a Content Set for an NSArrayController?

Heys,
I am writing something in Xcode. I use Core Data for persistency and link the view and the model together with Cocoa Bindings; pretty much your ordinary Core Data application.
I have an array controller (NSArrayController) in my Xib. This has its managedObjectContext bound to the AppDelegate, as is convention, and tracks an entity. So far so good.
Now, the "Content Set" biding of this NSArrayController limits its content set (as you'd expect), by a keyPath from the selection in another NSArrayController (otherAc.selection.detailsOfMaster). This is the usual way to implement a Master-Detail relationship.
I want to variably change the key path at runtime, using other controls. This way, I sould return a content set that includes several other content sets, which is all advanced and beyond Interface Builder.
To achieve this, I think I should bind the Content Set to my AppDelegate instead. I have tried to do this, but don't know what methods to implement. If I just create the KVC methods (objectSet, setObjectSet), then I can provide a Content Set for the Array Controller in the contentSet method.
However, I don't think I'm binding this properly, because it doesn't "refresh". I'm new to binding; what do I need to implement to properly update the Content Set when other things, like the selection in the master NSArrayController, changes?
However, I don't think I'm binding this properly, because it doesn't "refresh".
This most often means you are assigning directly to the instance variable, not using KVC-compliant accessor methods nor posting KVO notifications.
The general solution is to create accessor methods for the property and then use them everywhere, including inside that class, except in its init and dealloc methods.

setSelectsInsertedObjects on NSArrayController not actually selecting

I Have an NSArrayController bound to a NSUserDefaults controller, with setSelectsInsertedObjects set to YES in Interface Builder, but when I click Add, the previously select object gets unselected, instead of selecting the newly added object.
What am I missing?
How are you binding them? If it is through NSArrayController's 'content' binding, then I believe it tries to bind the selectionIndexes to the same object. This class (NSIndexSet) does not work with NSUserDefaults (I have no idea why, but I've had the same problem in the past - I think it has something to do with it's object lifecycle; it gets initialized as empty and then adds indexes or something). What setSelectsInsertedObjects is doing is just automatically updating the selectionIndexes when a new object is added, and basically your NSUserDefaults controller is messing that up. I'm not sure where it is, but I think if you hunt around NSArrayController's bindings you will find one for selectionIndexes (or something related) that was automatically bound to NSUserDefaults for you; if you uncheck that, things should work.
That's pretty much what selectsInsertedObjects means, as I understand it. When the user adds a new item, the new item is selected, replacing the previous selection.
If you want different behavior, you could extend NSArrayController or create your own controller class that uses NSArrayController as a delegate, perhaps based on NSProxy. I believe you'd need to override add: to:
save the current selection
call the parent add:
merge the current selection with the saved selection
set the selection to the merged selection
However, I don't know enough about NSArrayController internals to say whether this would work.

Doing something wrong with bindings, can't find out what

I've a mutable array that holds instances of a model object. That model object has several properties one being "name". I have no problems initialising or populating the mutable array.
I've a window with a drawer. I added a table to the drawer, the idea being that the drawer would use the table to display several instances of the model object.
I added an nsarraycontroller to the xib of the window that has a drawer. In the Array Controller Properties I've set the Object Controller to be an instance of the model class. On the Array Controller Bindings I set the Controller Content to point to the File Owner and set the Model Key Path to the name of the array.
On the table, I bind the content to the Array Controller, the Controller Key to arrangedObjects and Model Key Path to name.
My problem is that although the mutable array has been properly initialised and populated I can't see a single entry on the table on the drawer. Am I missing something here?
Two possibilities:
First: you might have bound the wrong thing (your description here is a bit ambiguous). Bind each table column's "values" to the array controller's #"arrangedObjects.propertyName" (like arrangedObjects.firstName for the First Name column, etc.). There are alternative ways to bind the whole table, but you probably aren't binding the column's values, just the table's content.
Second: it's also possible the accessor to your model object isn't KVO compliant. Make sure proper KVO notifications wrap your setter accessor for your model array. If you've #synthesize'd it, all should be well. If you've hand-coded your accessors, all might not be well. :-)
I assume you bound the tablecells table columns to the arraycontroller? I don't think Interface Builder will let you do anything else. Otherwise, it sounds like you have it configured properly.
I would recommend ibtool for troubleshooting these kinds of problems. It's a command line tool that does a text dump. You can inspect bindings in a more compact form than using the GUI in Interface Builder.