Using NSPopupButton and Cocoa Binding with predefined class - objective-c

I have a NSPopupButton. Also, I have a NSArray of objects with ClassA type, which is a predefined class and I don't have access to its code.
I'm trying to populate a NSPopupButton with this NSArray, so I used Cocoa Bindings. I set the content to the array, so for each item, it will use -description for the item label. But I want to use my own labels. How can I do this?
I already tried using "Content Values" alongside my custom NSValueTransform, that converts ClassA objects to NSString, but that didn't helped; it seems that my transform never called. I can't also apply my NSValueTransform to "Content" binding, because that breaks the "Selected Objects" (it passes the string instead of the actual object). For fixing this, I filled the "Content Objects" with proper values, but that didn't work, either.

Related

How do I access the properties of UITextField's selectedTextRange UITextPosition objects?

I'm trying to establish the start and end positions of a text selection of a UITextField instance, using its selectedTextRange property (as gained from the UITextInput protocol). However, I have no idea how to access the properties of the UITextPosition objects that make up the start and end properties of selectedTextRange.
Apple's docs on UITextPosition are woeful at this time, providing no methods or properties, though I know there are such properties in the object, because NSLogging one gives this:
<UITextPositionImpl: 0x6aaeb60>
<<WebVisiblePosition: 0x6aa40e0>(offset=5, context=([s|a], [u+0073|u+0061])>
In this example, the 'offset' is correct, and the context shows the characters either side of the selection point ('s' and 'a'), but I don't know how to access this nebulous WebVisiblePosition class. So, in short, is there a way of retrieving the details I want using UITextPosition objects from UITextField?
Of course, just after asking my question I found the answer, in this SO question: UITextPosition in UITextField.
It seems that when used as part of UITextField, the UITextPosition objects are not meant to be tinkered with directly, but used to feed other methods. In this case, the method offsetFromPosition:toPosition:, along with the text field property beginningOfDocument, can be used to return an NSInteger of a selection index.

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.

Bindings vs IBOutlets in Objective-C/Cocoa

I'm going through a simple Objective-C/Cocoa program to try and learn the language and am getting a little confused about how some things are linked from the code I write to the interface builder.
For example, I have a simple NSString:
#property (assign) NSString *letters;
And in my interface builder, I have a text field and I use the text field's bindings to connect it to letters.
However, the example also has this:
#property (assign) IBOutlet NSArrayController *wordsController;
In the view I have a table that continuously changes and shows different words and those words are stored in an NSMutableArray. I suppose I can understand that I just can't bind the array to the the table because there are some more complexities. So in the Interface Builder I create an Array Controller and bind it to the table. In the Array Controller's bindings, I bind the Array Controller to the array of words.
I understand that the last thing I have to do is also bind the Array Controller to my NSArrayController object as well. I don't understand why I do this through the main controller object by making a connection between this outlet and the wordsController. In the Array Controller's bindings section there's a greyed out option, Content Object, which says "An NSArrayController that the NSArrayController treats as its content." Why wouldn't I set the binding here? What is the significance of it being an outlet and why is it different than my NSString letters?
Thanks
You are confusing bindings and IBOutlets. This is not unreasonable -- it's a lot of Control-dragging of connections and it can be hard to keep clear what's going on. Let me try to explain:
Bindings are a way to let Cocoa handle the mechanics of keeping a model (some collection of data, even something as simple as a single NSString) and a view (an object which displays on the screen) in sync. When you "bind" your NSString to a text field's value, you are asking the framework to communicate changes to the string or the text field "behind the scenes"; your object which owns the string gets notified to change the string's value when the text field changes, and vice versa.*
A similar situation applies to your mutable array, array controller, and table view. You're essentially right about the complications: the mutable array and the table view don't know how to talk to each other; the array controller stands in between and facilitates: ("Okay, tableView wants to know what to put in row i. Array, give me your object at index i." :) In the past, you would've had to write that code manually, and it looked very similar every time you did so.
That's what the bindings do. They are a way to reduce boilerplate code. The IBOutlet to the array controller gives your object a way to send messages to the array controller, if necessary. One simple example of why you might need to do this is to allow a menu item to trigger a method in the array controller; a document object or another controller might handle the action from the menu item and call the appropriate message on the array controller. You can also ask the array controller for its arrangedObjects to get, for example, the sorted and filtered version of its content array.
* One side note here is that your NSString property should almost certainly use retain, not assign. The object that contains this variable should be responsible for its memory.

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.

Binding an NSPopupButton to an NSDictionaryController

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.)