NSDatePicker bindings - objective-c

Why property's value bound to NSDatePicker does not always reflect what control shows? There are at least 2 cases when my bound property gets different value than date picker shows:
Initial value. Date picker shows it's initial date (set in IB) but bound property returns nil (if user does not interact with picker).
Min value changes date in picker, but bound property still returns old value (the value user had set before min value was set).
What is the reason to use bindings over target/action if it does not support such fundamental behaviour? I'm new to Cocoa binding so maybe I'm missing something.
Update:
Attached sample project to see the problem.

Why property's value bound to NSDatePicker does not always reflect what control shows?
In Model-View-Controller pattern, your NSDatePicker is the view, your ViewController is the controller, and the NSDate property is the model.
You bind a view to a model via a controller. Not the other way round.
When you bind a view to a model via a controller, the view will start reflecting the model. If you interact with the view (e.g., change the date), the change will be applied to the model.
Initial value. Date picker shows it's initial date (set in IB) but bound property returns nil (if user does not interact with picker).
NSDatePicker shows whatever default value, because the model is not providing an NSDate object. NSDatePicker should NOT change the model without user interaction.
Min value changes date in picker, but bound property still returns old value (the value user had set before min value was set).
NSDatePicker just shows a default value within the range of acceptable values, because the model is not providing an NSDate object.
What is the reason to use bindings over target/action if it does not support such fundamental behaviour?
Target/action is just part of what bindings does automatically. Without bindings, you would create an action method in your controller (the target) to handle user interaction (e.g., user changing the date). In the action method, you would then change the model object. Also, on loading the view, you would sync the view to what you have in your model. Bindings eliminates a lot of this kind of code.

NSDatePicker cannot display an empty date. If you do not set the dateValue or bind the value to nil the control still displays a date value; it likes 12/02/1982.
Not being able to display an empty date and thus indicate a nil binding is irritating.
The following NSDatePicker subclass can show an empty date and represents a nil binding as such.
https://github.com/ThesaurusSoftware/TFDatePicker
Run the TFDatePickerTest target to see how it performs.

Related

How to add VoiceOver on NSTabViewItem?

Voiceover is typically implemented with accessibilityRole instance method. I was able to use it on a button for example
button.accessibilityRole = NSLocalizedString(#"Button", nil);
But when I use it on a NSTabViewItem,
NSTabViewItem *item0 = [NSTabViewItem tabViewItemWithViewController:self.tab0];
item0.acessibilityRole = NSLocalizedString(#"Tab 0",nil);
I get an error saying
Property 'accessibilityRole' not found on object of type 'NSTabViewItem *'
Edit: I also tried accessibilityLabel.
accessibilityLabel is usually called on Views, so I used it on NSView.
NSView * cellView = [NSView newAutoLayoutView];
cellView.accessibilityLabel = "label";
A number of issues here.
The primary problem is that accessibilityRole is a method which you override to return the role of your object. You can't set the role in the way you are attempting, you can only subclass an NSCell/NSView and return the appropriate string. However, you probably don't want to do that in this case because:
You don't return a localized value from a view or cell's accessibilityRole property, you use one of the NSAccessibilityRole types. And more importantly:
The role is only used to provide information about the kind of object VoiceOver has selected. In the case of tab views, the correct role for the tab view itself is NSAccessibilityTabGroupRole and each tab has NSRadioButtonRole and a subrole of NSAccessibilityTabButtonSubrole. This will happen for you automatically when you use an NSTabView. Note, you would never use a role of "Tab 0", which conflates the label with the role. And you don't need a label if the tab has a text title anyway, it would only be necessary if you had a tab with an icon for a title. And then the label would be something like "information" for an icon of ℹ️.
You generally shouldn't override the accessibilityRole on a view/cell, but instead instantiate the right sort of object to begin with. For instance, instead of creating a button with a standard type of NSButtonTypeMomentaryPushIn and then overriding its role to be NSAccessibilityCheckBoxRole, you should just create a check box. Then the role/subrole/role description will be set correctly for you. In general, the only time you would want to override the accessibilityRole on a view is if you are rolling your own view from scratch.
When setting accessbility attributes on a button or any other object with a cell, you shouldn't use the NSView (the NSButton instance). Instead, you need to use the NSCell (NSButtonCell, reached with button.cell). E.g. button.cell.accessibilityLabel = NSLocalizedString("Cancel", nil); While many AX attributes are passed through from the cell to the view, a few are not. Also, different accessibility technologies (VoiceControl, VoiceOver, SwitchControl) are more or less strict about this. You should always set things on the cell where appropriate to be compatible with widest range of AX technologies.
The best way to work out what your app should do is to find analagous UI in an Apple product and explore the AX hierarchy using Accessibility Inspector.

Objective-C: Date picker and level indicator

I'm using an NSDatePicker and NSLevelIndicator to try and set/display certain values of an object. I don't want to use bindings. My first thought would be to try and set a delegate of the date picker/level indicator to be my controller class so that I can be notified when either of those is changed. However, NSDatePicker and NSLevelIndicator don't have a delegate (at least, none that I can see in interface builder). How then do I keep track of when these things are changed?
NSControl and its subclasses use the target / action mechanism to alert you when their value changes. Some delegate protocols work in a similar manner, but in general delegates are used to modify the behavior of an object, while target / action alerts your controller of a change in a UI control.

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.

NSDatePicker - getting the value when it is changed

How would I get notified of a value of a NSDatePicker, when it's changed?
I've only done it on the iPhone with UIDatePicker but it is similar on the Mac, you register as a delegate and receive messages. See Apple's Docs here.
I should clarify, this tells you when it changed, you still need to call -dateValue to get the date.
The same you would any other control. Options are:
Target/Action
Binding
Observe notification
Delegate
You can bind the picker's value binding to a property of your controller, or a property of a model object (through a controller).

Can you Bind to the timeInterval attribute of an NSDatePicker?

I've got a Core Data application that has an Event class, which has a start date and a finish date. It's trivial to bind these to a pair of NSDatePicker widgets, but I wanted to make it work with the NSRangeDateMode available in Leopard.
The NSDatePicker has a pair of methods that deal with timeInterval, but I don't seem to be able to bind to this.
Update: I've used a manual call to do the binding, and it half works:
[picker bind:#"timeInterval"
toObject:array
withKeyPath:#"selection.timeInterval"
options:options];
It sets the timeInterval in the NSDatePicker when the underlying object is changed, but does not set the underlying object when the NSDatePicker's timeInterval is changed.
Sadly, no. The timeInterval property of the date picker is not even properly key-value observable. Basically, you're stuck either setting up an action method or using the delegate validation method to receive updates to its value. Also, you'll want to round it off to the nearest multiple of 86400.0 (i.e. the number of seconds in a day), since the date picker is consistently off by some fraction of a second in its reported timeInterval. Perhaps by the time Snow Leopard rolls around, this feature will be fully baked.
The interval support is only available when you're using the graphical version of the date picker. Even then, there's no native binding support for timeInterval.
Also depending on how you're intending to use this the UI to select ranges that extend past the current month is poor in my opinion.
1169097 explains how to implement custom bindings.