What's the cleanest way to bind enumerated properties? - cocoa

I have a menu which has an item for each value in an enum.
The menu:
[ ] Sort by Due Date
[ ] Sort by Priority
[√] Sort by Title
The enum:
typedef enum CW_TASK_SORT_METHOD {
CWTaskSortMethodDueDate,
CWTaskSortMethodPriority,
CWTaskSortMethodTitle
} CWTaskSortMethod;
The property:
#property(readwrite, assign) CWTaskSortMethod taskSortMethod;
What's the cleanest way to wire this up? I have two ideas but both strike me as unsatisfactory.
1st idea: Create properties for each value (sortMethodIsDueDate, setSortMethodIsDueDate: etc) These properties would call setTaskSortMethod: and call valueDidChange:#"sortMethodIsDueDate" etc. The menu items would then bind to these properties. This seems like a lot of work.
2nd idea: Connect each menu item to -(IBAction)updateSortMethod:(id)sender which could then iterate the menu items and set the value depending on sender. This approach is fine until taskSortMethod is changed by a different section of code at which point code needs to be added to keep the menu in sync with taskSortMethod.
I'm leaning towards the first approach as it has better separation between the V & C.
Any better ideas?

I think you're on the right track with your second idea, but there's something to consider:
NSMenu / NSMenuItem don't have a concept of "selected item" like NSPopUpButton for instance. I'd use the target/action mechanism to change the sort method and menu validation (Introduction to User Interface Validation) or even the NSMenu delegate method -menu:updateItem:atIndex:shouldCancel: (NSMenuDelegate Protocol Reference) to update the state of the item based on the result of -taskSortMethod.
Since the menu items only need to be updated when they're shown (which this mechanism does for you), you don't have to worry about updating the menu items yourself when -taskSortMethod changes elsewhere.
Ex:
[sortByDueDateMenuItem setState: ([self taskSortMethod] == CWTaskSortMethodDueDate) ];
IMO, this is a lot cleaner than trying to over-engineer a bindings-powered solution.

Related

How to sort NSArrayController using bindings from NSTableColumn?

There are similar questions to this already, but I haven't found any of them to work for me. All I want to do is re-sort the table view based on clicking the column header. And I don't want to do this programmatically because I don't think I have to. Anyways...
I have a NSTableView binded to an NSArrayController that is binded to my managedObjectContext.
For a specific NSTableColumn, I have the value binded to the Array Controller with the model key path "amount" which is an NSNumber.
I see in the attributes for this NSTableColumn, there is a "Sort Key" and a "Selector". I set the sort key to "value.amount" (not sure why, but I saw this elsewhere) and the selector defaults to "compare:" which seems fine.
Then for the NSArrayController, I see a binding called "Sort Descriptors" which I bind to "Shared User Defaults Controller". The "Controller Key" defaults to "value" (I assume this is why I used value before), and I set the "Model Key Path" to "amount.
This sounds reasonable, I suppose. But (1) it doesn't work and (2) what about when I want to sort based on two different columns? Also, (3) how do I set the default way to sort?
UPDATE:
OK, so I have the array controller binded to the managed object context. I have the table content binded to the array controller arranged objects, and selection indexes to the array controller selectionIndexes. I have a table column value binded to array controller arrange objects and the associated key. the sort key for that column is the same key with a compare: selector.
The table is not reordering when I click the headers though. Am I supposed to bind the sort descriptors of the table?
If possible, can you send me your example?
Thanks for the help,
Chet
First thing you should know is both the NSTableView and the NSArrayController can sort your items. You’re trying to set a mix of both right now, and there’s no point in that. For simple uses, just rely on the tableView.
So, don’t bind anything in the NSArrayController. For the NSTableColumns, don’t use “value.amount”, just use “amount”. “compare:” is fine for the selector.
I just created a tiny project (no code) to demonstrate this and it worked great with sorting.

How to bind hidden property in a control when a integer equals some certain value in OSX?

I've got a RadioGroup with 3 cells. I want to hide some controls when the selected index in radio group is 1. That is:
[someControl setHidden: radioGroup.selectedIndex == 1];
I've got a lot of controls will show/hidden when radio group selection changed. Some might show when the selected index equals 0, some might show when equals 2.
I want it to be done by binding, not connect each control reference using outlet.
How to acheive that?
There are at least two ways of doing this, as binding hidden requires a Boolean balue:
Create a property that is of type BOOL and returns YES or NO based on your value comparison, then in your class use KVO to observe the original value and set the Boolean property inside of the KVO observer (this is required to make sure the object is updated at the right time)
Use bindings alone, but create a Value Transformer to transform each value you need into a BOOL as necessary to be interpreted correctly. There is an existing value transformer that changes YES to NO and vice-versa, but for other value transforms you will have to create these yourself, and there is no good way to parametrize them inside of the xib file.
The first solution is probably easier.

How to bind objects in NSDictionary in NSUserDefaults?

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.

Basic Cocoa Bindings: Toggle a boolean from menu in IB

I'm just getting started with Cocoa Bindings and while I've read through much of the documentation, I'm still struggling to implement a basic feature, making me question wether I'm doing it wrong or perhaps it's just not possible via IB.
Goal: Have a menu item called "Toggle visibility" toggle the state of a Boolean property in my application controller (AppController) called "visibility." I think this should be possible completely through IB.
(Note: CoreData is not in use here)
What I think needs to happen is I need to have an NSObjectController who's content outlet is set to my AppController class. Then I think I need to use an Action Innvocation binding. But here's where I run into trouble.
Should the Controller Key be selection? Should the Model Key Path be the name of my Boolean Property? What should Selector Name be?
Or do I need to setup a separate action method called "toggleVisibility" that I can bind to? If I did, why wouldn't I just use standard target/action associations, rather than bindings?
Any input / direction appreciated.
Goal: Have a menu item called "Toggle visibility" toggle the state of a Boolean property in my application controller (AppController) called "visibility."
Your goal tells you why you are failing.
In your description of your goal, you say that you want to set this menu item up as a command, a verb, an imperative sentence—“toggle the visibility”. But that's not the right title for the menu item.
Moreover, implementing a verb menu item with Bindings is inherently difficult because Bindings is the wrong tool for the job. Bindings is for exposing properties in your UI. It isn't for performing commands.
The correct title is supported by the correct Bindings-based solution.
The correct title of the menu item is, simply, “Visible”.
This alone should give you a hint as to how to proceed. Here's another: When the selection is visible, the menu item should have a check mark; when it is not visible, the menu item should have no mark.
You want to bind a property of the menu item to, ultimately, a Boolean property of your controller. Normally, this property of the menu item would be state, but for whatever reason, the binding for it is named value.
So, bind the value binding of the menu item to the Boolean property of your controller.

Enabling NSButton with bindings, based on NSTableView selection

I have a NSWindow containing an NSButton and an NSTableView.
I'd like the button to be enabled if and only if the table contains at least one item, and exactly one item is selected. (The table does not allow multiple selection.)
What can I bind the button's enabled binding to to make this happen?
This is an old thread, but here are my 2 cents:
Use and array controller and bind the button's enabled state to
Controller Key: selectedObjects
Model Key Path: #count
Works fine.
Try binding to the array controller's selectedObjects, model key path count, with no value transformer.
Note that this would be unsafe if you allowed multiple selection: For one thing, the count could easily be neither YES nor NO; for another, if the user selected a multiple of 256 items, the lowest byte of the count would be 0, so the BOOL value would be NO even though there is a selection.
I ran into this today and I got it to work after some efforts.
My button should be disabled if nothing is selected in the "Master Table":
Problems I ran into:
Use the actual button and not the enclosed Button cell
Specify NO = disabled for Multiple values, No selection etc.
Bind the Enabled property to the Master Table's selection and use a property (code in my case), which is present.
Use the transformer NSIsNotNil to enable the button if something is selected in the master table.

Resources