I want to bind selection of NSPopUpButton to one of the predefined values. To make it simpler, imagine a blog app: I'd have BlogPost and Category entities (Core Data, although it doesn't matter) and each BlogPost object has a link to one of the Category objects (through category property).
I want to have the user change the category through NSPopUpButton, so in my XIB, I have NSArrayController that holds all possible categories and I bind button's Content Values to:
Bind to: categoriesArrayController
Controller key: arrangedObjects
Model key: title
This nicely populates pop up with titles of all categories. I can also bind Selected Object to:
Bind to: blogObjectController (or directly to Blog object)
Controller key: selection
Model key: category
This works and correct category is selected in pop up, however changing selection doesn't change the category under which the blog post is filed, but instead changes the title of post category to the selected value.
Given the above bindings this actually makes sense, so my next step was to change the binding of pop up's content values to only arrangedObjects (no model key), similarly selected object. This works and changing selection does indeed change category of the blog post to another one. BUT it doesn't show category title in pop up menu, but instead shows the description of the category (which in Core data ends with something like <Category 0x1002b6990> (entity: Category; id: .......).
Which also makes sense, so I introduced custom NSValueTransformer (non-reversible). This fixes item descriptions in pop up menu, but then selection doesn't work. I also tried using the transformer on selected object binding, but that just disables my pop up entirely (perhaps reverse transform would be required, but this would substantially complicate transformer).
Am I missing something obvious - is this possible to achive entirely with bindings, without introducing additional code on controller layer?
Any though is welcome!
PS: hope above text makes sense :)
I would try it as follows:
Of NSPopupButton:
Bind Content to : categoriesArrayController ControllerKey: arrangedObjects
Bind Content Values to : categoriesArrayController ControllerKey: arrangedObjects Model Key Path: title
Bind Selected Object to : blogObjectController ControllerKey: (empty) Model Key Path: content.category
Related
This is a macOS app, using Core Data, no Documents, and no Storyboards. I'm trying to use NSArrayControllers & bindings to display my data.
The data model 2 entities:
Book (which has attributes like author, title, blurb), and
Chapters (which has attributes like title)
The two entities are connected with a one-to-many relationship:
Book.chapters <—>> Chapter.book.
The app can have multiple books, which it shows in a tableView. Simple enough. I have an Array Controller set up in XCode called "Books Array Controller". I set its Entity as "Book" and check the "prepares content".
It's a master-detail, where one of the properties on the detail is an array to be shown in its own table. And this is what's giving me problems.
I bind the Books Array Controller as follows:
1. The managedObjectContext is bound to Delegate's self.managedObjectContext.
2. The bookListTableView has its Table Content > content bound to "Books Array Controller" controller key = arrangedObjects.
3. And then for each column in the table, I select the textViewCell and bind its value to the "Table Cell View" model key path = objectValue.title, objectValue.author, etc.
4. I have a textView that the "blurb" (attributed string) of the selected book by binding Attributed String to "Books Array Controller" controller key = selection, model key path = blurb.
All of this works just fine.
But I'm having a lot of problems trying to figure out how to display the chapter list in another table. I've tried creating another Array Controller called "Chapters Controller". I set its Entity as "Chapter" and check the "prepares content".
I bind the Chapters Controller as follows:
1. The managedObjectContext is bound to Delegate's self.managedObjectContext. (just like for the other one)
2. The Controller Content > content set is bound to "Books Array Controller" controller key = selection, model key path = chapters.
3. I bind the table's content to the "Chapters Controller" controller key = selection, model key path = chapters.
4. And then for each column in the table, I select the textViewCell and bind its value to the "Table Cell View" model key path = objectValue.title, etc.
Nothing ever shows in that 2nd table, the one for the chapter list. Not even a bunch of "Table View Cell" things. It's empty. I've tried a bunch of variations I won't bore you with. There's clearly something I'm missing. Any help?
The app correctly makes its Book objects, and the Books appear to correctly make their Chapter objects.
———
Update: I've attempted to follow Willeke's advice and am still not doing it right.
Here are the bindings...
Books controller binding:
Chapters Controller binding:
Books TableView binding:
Chapters TableView binding:
The content set of the Chapters array controller is bound to the selection of the Books array controller. When you select a row in the Books table view, the Books array controller's selection has to be synchronized by binding the selections indexes. I usually bind Content to arrangedObjects, Selection Indexes to selectionIndexes and Sort Descriptors to sortDescriptors.
The bindings from the Chapters table view are the same as the Books table view. Bind content of the Chapters table view to arranged objects of the Chapters array controller.
Hey I am new to mac development and I want to use bindings (xcode 5.1.1).
I want to set the Title of a radio button dynamically by an entry of an array controller. I am looking for something like a syntax description how I can perform it.
e.g. something like value1 WHERE value2="bla"
If I trying to search at google I always find solutions which did it programmatically.
Is there anywhere some examples which show me the syntax I can use in this field?
The picture below should you show what I mean.
Answering the question as clarified in the comments…
First, bindings is not always the right technology. It can simplify some things, but it can't do everything and even for some of the things that it can do, it doesn't necessarily make them simpler.
Radio buttons are often organized in an NSMatrix. In that case, you can bind the matrix bindings to track the selection. There are three content-related bindings for a matrix, which can be kind of confusing. The "content" binding is the base. In some cases, it's sufficient. However, if there's a distinction between the object being bound and the value that should be shown by the cells of the matrix, then you can bind the "contentValues" binding to be a subpath of the content binding. That is, it needs to be the same as the content binding with possibly additional elements added to the end of the model key path.
Furthermore, if you want the selected object to be distinct from the content object, you can bind "contentObjects" to a subpath of the content binding.
For example, there may be an array controller whose content is a bunch of Person objects. The matrix content binding might be bound to that array controller's arrangedObjects. If you leave it like that, the cells of the matrix will be populated from the description of each Person object. However, you could bind the matrix's contentValues to the array controller, arrangedObjects, model key path fullName. Then, the matrix cells will be populated with the full name of each Person object.
If you then bind the matrix's selectedObject binding to a property on your window controller, that property will be set to the selected Person object each time the matrix selection changes. If you would prefer, you could bind the matrix's contentObjects binding to the array controller, arrangedObjects, model key path uniqueID. In that case, the window controller property would not be set to the selected Person object itself, but to its uniqueID property.
Alternatively, you could bind the matrix's selectedIndex binding to a controller property. If you use the window controller, then that just directly sets a property on the window controller to indicate the index of the matrix's selection. Or you could bind it to the array controller's selectedIndex property, in which case the selection is "stored" in the array controller.
You need a keypath that takes no parameter as described in the key-value coding (KVC) reference.
By binding to an array controller's selection, if the selection collection is one object with a property or method "value1," then the binding runtime is calling the method valueForKeyPath:#"value1".
The NSObject protocol has performSelector:withObject, but there is nothing like valueForKeyPath:withObject in the KVC protocol or the NSKeyValueBindingCreation protocol
That said, registering dependent keypaths can provide some equivalent behavior...
+ (NSSet*) keyPathsForValuesAffectingValue1
{
return [NSSet setWithObjects:#"value2",nil];
}
... and that would ensure that any time value2 changes, the binding to value1 is re-evaluated.
How do I bind NSDictionary objects to several text fields in Interface Builder?
I would like to bind each object to a specific item in the dictionary. For example, the first text field should be bound to Actor:
You need to add a NSObjectController in Interface Builder. In the attributes inspector tab leave the standard mode "Class" and class name on "NSMutableDictionary", but switch "Prepares Content" on. In the bindings inspector bind "Content Object" to "Shared User Defaults Controller", controller key "values" and "Model Key Path" to your dictionary's key in User Defaults. Switch "Handles Content As Compound Value" on.
Now you can bind your text field (or check boxes etc). values to the ObjectController, with controller key "selection" (!) and the keys in your dictionary (as model key path).
Hope this helps. I couldn't find the answer in Apple's documentation and also not in the net, was lucky to find it out myself by trying different controller objects...
First: #keeluu's answer should be marked correct.
Doing it Programmatically
If you're doing this in code rather than through IB, there's a "gotcha" to look out for. It looks like this:
// Assume 'anObjectController' is an NSObjectController.
// Assume 'userDefaultsController' is [NSUserDefaultsController sharedUserDefaultsController]
// Assume 'someDictionary' is an NSDictionary in userDefaults.
[self.anObjectController
bind:NSContentBinding
toObject:userDefaultsController
withKeyPath:#"values.someDictionary"
options:#{NSHandlesContentAsCompoundValueBindingOption: #YES}];
If you do the above, you'll find that when you bind your UI elements to properties in someDictionary, those UI elements will correctly display the values that are IN user defaults, but when you change them (by say, clicking a bound checkbox) those values will NOT be updated in user defaults; the old values stick around.
The Reason
The binding must be to NSContentObjectBinding instead of NSContentBinding. The NSObjectController class exposes a binding named contentObject. When you bind to that, everything works properly.
This is an easy mistake to make because Xcode's code completion automatically goes for NSContentBinding and it's one of the most frequently used binding names anyway. This just cost me hours of trouble, so hopefully I saved someone else some time.
Try to add NSDictionaryController to you .xib file, then bind its Controller Content to your dictionary in User Defaults. Then you can bind text field values to Dictionary Controller (arrangedObjects key).
I never tried that but I think it must work.
Hope, it helps.
I'm exploring bindings right now, and have an NSPopUpButton -
It presents me a number of options for bindings under Value Selection - Content, Content Objects, Content Values, and then Selected Object, Selected Value, and Selected Tag. Could someone please explain the difference between these?
Those are explained in the Cocoa Bindings Reference for NSPopUpButton, although that reference is not quite clear.
Content is an array controller that provides elements to the popup button. The array controller should be bound to an array. In order to determine how each element in the array is shown in the popup button, -description is sent to each object in the array.
You may customise this in two ways:
If you want the Selected Object binding to provide an object distinct from the array elements managed by the array controller to which Content was bound, you can bind Content Objects to another array controller. It could also be the same array controller but with a different key path;
If you want the popup button options to be something different than the description of each element in the array managed by the array controller to which Content was bound, you can bind Content Values to another array controller that manages an array whose elements contain the popup options. It could also be the same array controller but with a different key path.
A simple example: suppose you have the following class:
#interface Customer : NSObject
#property (copy) NSString *name;
#property (copy) NSString *phoneNumber;
#end
and you haven’t overridden the -description method. In this case, -descriptionis useless and the name property would be a good choice for the popup options. You’d bind:
Content to an array controller that manages an array of Customer instances, controller key arrangedObjects;
Content Values to the same array controller, controller key arrangedObjects, model keypath name.
You can then bind Selected Object to something else, for example a property in your application delegate or window controller. Cocoa bindings would then assign the selected Customer instance to that property.
Now suppose you are not interested in the whole Customer object that’s been selected, but only its phone number. In this case, you can bind Content Objects to the same array controller, controller key arrangedObjects, model keypath phoneNumber. When a popup option is selected, Cocoa bindings will set phoneNumber instead of an entire Customer instance. In summary: if you don’t bind Content Objects, Selected Object represents the original object in the array. If you bind Content Objects, then Selected Object can be something different.
You’d bind Selected Value if you were not interested in the original objects (or the content objects), but the actual strings shown in the popup options according to the Content Values bindings.
Quick recipe for providing data to the popup button:
Bind Content if you have objects (not only strings) that represent the popup options;
Bind Content Values if the options that are shown to the user cannot be obtained via Content by sending -description to the array elements;
Bind Content Objects if you want Selected Object to return something different from the array elements from Content.
Quick recipe for obtaining the current selection in a popup button:
Bind Selected Object if you want to know the full object (either from Content or Content Objects) representing the current popup selection;
Bind Selected Value if you only want the string that’s currently selected in the popup.
And lastly, you’d use Selected Tag if the popup options are actually taken from a menu whose items have a tag set.
#Object refers to any KVC-compliant object. #ObjectValue refers to the key path used to get the value from that object.
So, for your pop-up binding, ContentObjects would be bound to, say, an NSArrayController's arrangedObjects. Say this refers to an array of dictionaries or managed objects. You can't meaningfully present a dictionary in a pop-up (you get the start of the description output, e.g <NSCFDictionary... or similar), so this is where the contentValues binding comes in. This would be something like your NSArrayController's arrangedObjects.name, where name is a key from your dictionary or managed object.
I hope this helps, I struggled with the same concept myself when I started with bindings.
Is it possible to set a default selection on an NSPopupButton? I have one that allows the user to select the type of server they want to set up, but since an NSPopupButton always shows the first item, they may ignore it if that's the type they want. However, even though that item is being displayed, calling -selectedItem returns (null). Everything works fine if the user picks an item from the menu first.
The Button's content and contentValues are bound to the same Array Controller, which in turn is bound to the keys property of an NSDictionary. I've tried binding the selectedIndex to a variable in the controller and updating that in code, but it has no effect. (I may just be binding it wrong...) How can I select the first item by default?
Thanks in advance!
SphereCat1
When using Bindings, you don't need to and shouldn't get any model info—neither the model itself nor selection state—from the views directly. Talk to the controller that owns the model and the selected indexes.
Note that “index” doesn't have any meaning for an NSDictionary, and keys is not a property of an NSDictionary. (Indeed, I would not be surprised if you were to get an exception because your dictionary does not have an object for the key “keys” in it.) It is a method, and not the accessor kind, so while you can ask the dictionary for the value of that method using Key-Value Coding, you should not.
What you should do is make model objects representing the server types, and hold an array of those, and bind the array controller's content to the property whose value is that array. Bind the pop-up button's contentValues to a name property of your model objects, which should hold the localized name of each server type.