9

I have two windows: Window A is loaded from NIB; and Window B is created programmatically.

Both windows have a NStextView: the attributedString of the textview in Window A is bound to the the property text of a model using IB; while the attributedString of the textview in Window B is bound to text property of the model using -[NSObject bind:toObject:withKeyPath:options:] method.

[textview bind:@"attributedString"
      toObject:obj
   withKeyPath:@"text"
       options:nil];

Here is the weird thing: the textview in Window B is indeed bound to the obj.text, but the changes in the textview is never updated to obj.text. But, if I made changes in the textview of Window A, the obj.text and the textview in Window B are updated accordingly.

So I am thinking, the -[NSObject bind:toObject:withKeyPath:options:] method is only for one-way binding. I couldn't find a clear explanation in the Cocoa documentations. Does any one have experience with this problem? How do you implement two-way binding in code?

Demitri
  • 13,134
  • 4
  • 40
  • 41
Jensen
  • 1,653
  • 4
  • 26
  • 42

2 Answers2

5

Cocoa bindings are inherently two-way, but certain behaviors (like continuous/live updating of text fields) require specific options to be set. In IB you will want to make sure that the "Continuously Updates Value" check box is checked. To get equivalent behavior programmatically, you will need to specify options on the binding. That might look something like this:

[textview bind: NSAttributedStringBinding 
      toObject: obj 
   withKeyPath: @"text" 
       options: (@{ 
                 NSContinuouslyUpdatesValueBindingOption : @YES })];

It's worth mentioning that when setting up a binding programmatically, it's worth checking an equivalent binding in IB and making sure you're passing all the same settings to the programmatic binding. For instance, I see in IB that the "Allow Editing Multiple Values Selection", "Conditionally Sets Editable", and "Raises For Not Applicable Keys" options are all checked by default for an NSTextView's Attributed String binding. That would mean our programmatic binding should probably really look like:

[textview bind: NSAttributedStringBinding 
      toObject: obj 
   withKeyPath: @"text" 
       options: (@{ 
                 NSContinuouslyUpdatesValueBindingOption : @YES,
                 NSAllowsEditingMultipleValuesSelectionBindingOption : @YES,
                 NSConditionallySetsEditableBindingOption : @YES,
                 NSRaisesForNotApplicableKeysBindingOption : @YES })];
ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • This still was not working for me. I found the answer (for my case) here --> http://stackoverflow.com/questions/1169097/can-you-manually-implement-cocoa-bindings – Just a coder Jun 24 '13 at 21:43
  • I'm not sure about the relevance of the linked question above. The binding mentioned here is between two NSControl objects -- no custom binding implementation required. – stevesliva Mar 27 '14 at 20:26
  • What do you mean by inherently two-way? I have a binding between two objects that only works one-way. Is this a special case with IB? – Julian F. Weinert Jan 19 '21 at 16:55
  • @JulianF.Weinert I mean that if you have a control bound to a model object (via a Controller in the common case) the field will populate its value from that model object (i.e. read) and then any changes the user makes in the control will be pushed back into the model object (i.e. write). That's all. – ipmcc Jan 19 '21 at 17:28
  • Do you know in which layer this feature lays? I guess it would be `NSControl`, right? For vanilla `NSObject` subclasses it only works unidirectional – Julian F. Weinert Jan 19 '21 at 22:42
  • I've not been working with Cocoa Bindings for a long time (5+ yrs now), but from my experience, "which layer?" isn't really a germane question. KVO (upon which bindings are based) is a fundamental part of `NSObject`. The various UI functions (focus, text input, etc) are a fundamental part of `NSControl` and friends. Bindings exist kind of 'in the middle' of those two. They observe key/value changes from one side and then push corresponding key/value changes into the other side. The real black magic of Cocoa Bindings is in the `NS*Controller` hierarchy. – ipmcc Jan 20 '21 at 22:01
1

Yes, bind: toObject: withKeyPath: options: is one way and no option can influence this. This is not the same as you make binding in .nib file. As usual Apple forgot to mention such simple thing in its docs. The simplest solution here is to create reverse binding at the same time when you create forward one. This will NOT dead loop your code when you assign the value. Here is the example with user defaults:

// two way binding of MyObject.myValue to user defaults
[NSUserDefaultsController.sharedUserDefaultsController.values bind: @"myValueInDefaults"
                                                          toObject: myObject
                                                       withKeyPath: @"myValue"
                                                           options: @{@"NSContinuouslyUpdatesValue":@YES}];

[myObject bind: @"myValue"
      toObject: NSUserDefaultsController.sharedUserDefaultsController
   withKeyPath: @"values.myValueInDefaults"
       options: @{@"NSContinuouslyUpdatesValue":@YES}];
Oleg Korzhukov
  • 606
  • 6
  • 19
  • Do I need to use two NSValueTransformers, if I need to convert one value into another and vice-versa, for the key NSValueTransformerBindingOption, or one would suffice? – rraallvv Mar 29 '15 at 06:46
  • As these bindings are not connected with each other, I think you need two NSValueTransformers, one for each binding. But I have not used NSValueTransformerBindingOption and cannot tell for sure. – Oleg Korzhukov Mar 30 '15 at 12:58
  • I see, for instance from degrees to radians and vice versa, I think there would be needed two of them, thanks – rraallvv Mar 30 '15 at 13:03