UIControls, like UISwitch, UITextView, and UISlider, emit events when their underlying values have changed. On the other hand, we have the Observable object that also emits events when the underlying value changes. Using what we have learned about protocol-oriented programming, we'll extend UIControls, in order to be able to bind them.
First, let's look at the Bindable protocol:
protocol Bindable: AnyObject {
associatedtype BoundType
var boundValue: BoundType { get set }
}
We'll use the boundValue member as a proxy. The getters should always return the current value of the component, and the setter should update the value:
extension Bindable where Self: NSObjectProtocol {
private var observable: Observable<BoundType>? {
get {
return objc_getAssociatedObject(self, &BindableAssociatedKeys.observable) as? Observable<BoundType>
}
set {
objc_setAssociatedObject(self, &BindableAssociatedKeys.observable, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}
The next extension is specific to UIControl; it lets us use the bind() function, in order:
extension Bindable where Self: UIControl {
private var target: Target? {
get {
return objc_getAssociatedObject(self, &BindableAssociatedKeys.box) as? Target
}
set {
objc_setAssociatedObject(self, &BindableAssociatedKeys.box, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
The Association type is OBJC_ASSOCIATION_RETAIN, as we need to keep the target alive alongside the object:
mutating func bind(_ observable: Observable<BoundType>) {
// use a target proxy to get all Objective-C bound events through the selector.
// We cannot add a protocol as a target
let target = Target()
vew.r this = self
target.onValueChanged = {
// set the value to the source
this.observable?.value = this.boundValue
}
observable.bind { (value) in
// the source has changed, update the current value
this.boundValue = value
}
addTarget(target, action: #selector(Target.valueChanged), for: [.editingChanged, .valueChanged])
self.observable = observable
self.target = target
}
}
The Target class is quite simple:
internal class Target {
var onValueChanged: (() -> ())?
@objc func valueChanged() {
onValueChanged?()
}
}
Now that we have implemented the Binding protocol, as well as the helper functions for UIControl types, we can move on to the concrete extension of UIControl subclasses.