Search code examples
swift2couchbase-lite

How to implement two way binding with Swift?


How to implement a two way data binding using Swift 2.0?

Let's assume I have the following model class (using couchbase lite):

@objc(Person)
class Person: NSObject{
    @NSManaged var firstName: NSString?
    @NSManaged var lastName: NSString?
}

And I like to bind the properties to a given formItemDescriptor like this:

class FormItemDescriptor {
    var tag: String?
    var value: Any?
}

How would I bind the FormItemDescriptor.value property to the Person.firstName property using 2-way-binding?

So changes in the model appear in the form and vice versa?


Solution

  • Swift does not support bindings out of the box, but you can achieve them with frameworks like ReactiveKit.

    For example, when you declare an observable property you can bind it to another observable property.

    let numberA: Property<Int>
    let numberB: Property<Int>
    
    numberA.bindTo(numberB)
    

    To do bi-directional, just bind in other direction too:

    numberB.bindTo(numberA)
    

    Now, whenever you change any of them, the other will change.

    Your example is bit harder though because you need to do bindings from @NSManaged properties that cannot be made Observable. It's not impossible, but to work the properties of Person should support key-value observing (KVO).

    For example, given a Person object and a label, you could take an Property from KVO-enabled property with the method rValueForKeyPath and then bind that observable to the label, like this:

    let person: Person
    let nameLabel: UILabel
    
    person.rValueForKeyPath("firstName").bindTo(nameLabel)
    

    If you have an intermediately object like your FormItemDescriptor, then you'll have to make its properties observable.

    class FormItemDescriptor {
      var tag: Property<String?>
      var value: Property<Any?>
    }
    

    Now you could establish binding

    let person: Person
    let descriptor: FormItemDescriptor
    
    person.rValueForKeyPath("firstName").bindTo(descriptor.value)
    

    Because firstName is not observable, you cannot do binding in another direction so you'll have to update it manually whenever value changes.

    descriptor.value.observeNext { value in
      person.firstName = value as? NSString
    }
    

    We also had to do cast to NSString because value is of type Any?. You should rethink that though and see if you can make it a stronger type.

    Note that UIKit bindings are coming from ReactiveUIKit framework. Check our documentation of ReactiveKit for more info.