Two-way binding. Interstellar FRP - Cocoa
A small experiment how to two-way bind a String property to a NSTextField.
References:
How to use Functional Reactive Programming without Black Magic - Slide num. 40
var observableKey: UInt8 = 0
extension NSTextField: NSTextFieldDelegate {
// text is a readonly property (proxy). We create an observable string when text property is read for the first time and add the observable string as a custom property of the NSTextField.
// To be able to receive the notification when text in text field changes we set self as the self delegate.
// controlTextDidChange(:) is called when the text changes where we can update the observable string with the textField.stringValue
public var text: Observable<String> {
var observable: Observable<String>
if let existing = objc_getAssociatedObject(self, &observableKey) as? Observable<String> {
observable = existing
} else {
observable = Observable<String>()
self.delegate = self
objc_setAssociatedObject(self, &observableKey, observable, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return observable
}
// Update text (observable string) with textField.stringValue when text in text field changes
override open func controlTextDidChange(_ notification: Notification) {
guard let textField = notification.object as? NSTextField else {
return
}
self.text.update(textField.stringValue)
}
}
// Two-way binding between CustomStringConvertible and NSTextField
extension Observable where T: CustomStringConvertible {
func bind(to textField: NSTextField) {
self.subscribe { stringConvertible in
textField.stringValue = String(describing: stringConvertible)
}
textField.text.subscribe { string in
guard let value = string as? T else {
return
}
self.update(value)
}
}
}
// Usage:
class ViewController: NSViewController {
@IBOutlet weak var textField: NSTextField!
let text = Observable<String>()
override func viewDidLoad() {
super.viewDidLoad()
self.text.bind(to: self.textField)
}
}
Download demo project here