Using Observables with ViewModels

The Observable and Binding protocols help to reduce the boilerplate that's necessary when binding a particular view or control to the viewModel's properties.

Let's suppose that we have a simple form for registering users, with their name, password, and age. It can be represented with the following ViewModel:

class SignupViewModel {
var username = Observable<String?>(nil)
var email = Observable<String?>(nil)
var age = Observable<Double>(0.0)
}

extension SignupViewModel {
var description: String {
return """
username: \(username.value ?? "")
email: \(email.value ?? "")
age: \(age.value)
"""
}
}

With the ViewModel configured, we can take a look at SignupViewController. It is quite simple, with a few text fields and a stepper to set the age:

class SignupViewController: UIViewController {
@IBOutlet var ageStepper: UIStepper!
@IBOutlet var ageLabel: UILabel!
@IBOutlet var usernameField: UITextField!
@IBOutlet var emailField: UITextField!

var viewModel: ViewModel! {
didSet {
bindViewModel()
}
}

func bindViewModel() {
// Ensure the view is loaded
guard isViewLoaded else { return }

ageStepper.bind(viewModel.age)
usernameField.bind(viewModel.username)
emailField.bind(viewModel.email)

// Bind the viewModel.age value to the label
// The stepper update the viewModel, which update the label
viewModel.age.bind { (age) in
self.ageLabel.text = "age: \(age)"
}
}
}

The view model will always stay in sync with the view representation. The two-way data binding guarantees that events from the model are properly propagated to the views that display them on screen, and also that each user input is properly stored in the view model.

The view layer is really dumbed down, to the point of simply being responsible for binding the view model. With the help of the Observable and the Binding protocol, the amount of boilerplate was reduced to a minimum, and the implementation in the view controller is straightforward.