1

In a macOS Cocoa Application if I have two NSSlider objects, I can set the following binding programmatically:

@IBOutlet weak var firstSlider: NSSlider!
@IBOutlet weak var secondSlider: NSSlider!

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Insert code here to initialize your application

    self.firstSlider.bind(NSBindingName.init(rawValue: "value"), to: self.secondSlider, withKeyPath: "value", options: [NSBindingOption.continuouslyUpdatesValue : true])
}

This works, but not continuously updating, unless I set the Continuous checkbox in the inspector in interface builder for the sliders. My problem is that I would like to set the binding between other properties that do not expose a continuous setting by themselves, specifically two NSTableColumn instances. Is it possible to bind continuously by setting an option in the binding code?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Alfonso Tesauro
  • 1,730
  • 13
  • 21
  • 2
    "My problem is that I would like to set the binding between other properties that do not expose a continuous setting by themselves, specifically two NSTableColumn instances." What do you mean by this, exactly? The "continuous" option for a slider makes it so that it updates bindings whenever its moved, rather than only updating when it's "let go". `NSTableColumn` wouldn't have any counterpart to that – Alexander Nov 18 '19 at 21:08
  • I am trying to keep two NSOutlineView objects in perfect sync. So I am interested in the "width" keypath of two NSTableColumn objects. I am currently solving the problem by using KVO and updates are delivered continuously. By using programmatic bindings I was hoping to achieve a cleaner implementation. Thanks a lot for your answer. – Alfonso Tesauro Nov 18 '19 at 22:45
  • Ah, that's some key information, I think you should edit it into your question. IDK if those fields supporting binding. If they don't, I'd just recommend to us RxSwift/RxCocoa, which present a single unified stream interface over KVO, NSBinding, NSNotificationCenter, delegates, and closure callbacks – Alexander Nov 19 '19 at 00:32
  • 1
    Without digging into this problem, I suspect it would be far easier to create a `float` property on your controller, then bind both of the slider's to this property (no programming required). Any change in one slider will update the property, which will change the other slider. Remember to set the "continuous" attribute of the value binding. – James Bucanek Nov 19 '19 at 01:13
  • “currently solving the problem by using KVO and updates are delivered continuously. By using programmatic bindings I was hoping to achieve a cleaner implementation.” Sort of a distinction without a difference. Cocoa bindings _are_ KVO. – matt Nov 19 '19 at 03:38
  • @matt: Bindings certainly makes heavy use of KVO (and indeed KVO/KVC was introduced to support Bindings), but Bindings builds significant additional functionality on top of KVO. And while it's an implementation detail, there are significant parts of bindings functionality that could be implemented in terms of (public/standard) KVO, but are in fact not. To OP's point, using bindings vs. "programmatic KVO" can mean writing significantly less code, which is surely what he's trying to achieve, and which is basically the whole point of bindings. – Andrew Madsen Nov 19 '19 at 23:17

1 Answers1

1

The short answer to your question is no, this is not possible. You should just use your own KVO code as you've already discovered.

Longer answer follows:

Fundamentally, the issue is two fold: NSTableColumn does not have a continuous property. There are classes that do (e.g. NSControl and NSCell), but NSTableColumn doesn't inherit from these. Technically, this property is meant to determine whether a control sends its action method to its target continuously, rather than specifically being about bindings, though it does also affect binding behavior.

The .continuouslyUpdatesValue binding option means something slightly different. It is used to determine whether bound value is updated continuously during user interaction or only when the UI element resigns its responder status (ie. user interaction stops). This is mostly useful for text field/view bindings.

Unfortunately, NSTableColumn can't really be a responder, at least in the traditional sense. And this option has no effect on bindings from an NSTableColumn's width.

Confusingly, this option also has no effect on bindings from NSSlider. Instead, if NSSlider's continuous property is false, it will always only update the binding on mouse up, even if the .continuouslyUpdatesValue option is set on the binding. Conversely, if NSSlider's continuous property is true, it will always update the binding's value continuously, even if the .continuouslyUpdatesValue option is set to false on the binding.

Some digging with the debugger explains this behavior. NSSlider always updates its bindings value as a side effect of sending its action to its target (regardless of the .continuouslyUpdatesValue option). This explains why its continuous property solely determines its behavior.

On the other hand, while NSTableColumn updates its width property and thus emits KVO notifications continuously as its width is changed, it only updates its width binding as a side effect of posting the columnDidResizeNotification that it posts on width changes. This notification is only ever posted at the end of a resize, not during a resize. Thus, NSTableColumn only ever updates its binding at the end of a resize.

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97