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.
Related
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.
I have a question about Core Data. When starting my appliction, when is my data (which is stored automatically by Core Data) loaded into the NSArrayControllers? I want to modify it in the first place before the user can interact with it.
To be more specific: I have an NSArrayController for the entitity Playlist. Before the user can add new playlists or interact with the app at all, I want to modify the playlists programmatically. I tried windowControllerDidLoadNib: in my NSPersistentDocument (MyDocument.m) and awakeFromNib both in my NSPersistendDocument and the NSArrayController, but when I check in these methods with [[myArrayController arrangedObjects] count] I get 0 as result (the array controller's content is empty).
However, I actually have data stored and it is displayed to the user. I just do not know when and where I can modify it in the first place.
Thank your for any help.
Data is never "loaded" into the NSArrayController. The array controller is not an array itself. It does not contain or otherwise store data.
Instead, the array controller queries the object it is bound to for specific pieces of data only when that specific data is needed. This is especially true of Core Data in which managed objects are only fully instantiated when their attributes are accessed. The array controller moves data from an array type data structure to another object (usually an UI element.)
If you want to modify an existing store before it displays in the UI, you need to process the data before the array controller used by the UI is even initialized. If you're using NSPersistentDocument, then you can override readFromURL:ofType:error: to fetch and modify all your objects when the document is first opened. Alternatively, you can override the window controller's windowWillLoad or showWindow methods.
Regardless of where you do it, you must fetch all the managed objects you want to modify. You could programmatically create an array controller to do this but a fetch request is easier to micro manage if you have a large number of objects to modify.
You could try observing the "arrangedObjects" keypath of the controller and adding some logic to work that your array controller has been populated for the first time.
Another possible hook is implementing the awakeFromInsert/awakeFromFetch methods of your managed objects.
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.
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.
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.)