MVVM — Bind views
An approach using vanilla Swift
Hi developer. What I am about to show you is the way I normally hook ViewModel
properties to the Views/ViewControllers
using Swift. If your project is using SwiftUI and/or Combine
, this is probably not relevant to you. However, the concept of binding views to property changes is quite similar. I’ll try to be as clear and concise as possible, so details such as adding UIViews to the hierarchy or DispatchQueues are looked over.
Before I go any further, a quick disclaimer — This is an approach I’ve found useful, that doesn’t mean it’s right or wrong whatsoever.
Said that, these are the actors we’re going to see throughout this post:
- M — Person
- V — ViewController
- VM — ViewModel
The feature to build consists of fetching the person’s information and displaying it on the labels — this concept can be extrapolated to any kind of views, tables, collections, etc. The following is the rudimentary implementation of an MVVM pattern.
Let’s say ViewModel
needs to fetch the person’s information from an API or any external service(which I am not delving into in this post) for the sake of this post, we’re hard-coding it. Once the data is retrieved, the ViewModel
executes the block onPersonChanged
to notify the view that the data has been updated.
class ViewModel {
var onPersonChanged: ((Person) -> Void)?
func fetchPosts() {
// these values are hard-coded. Not relevant(to this post)
// where this data is coming from.
let person = Person(id: 1, name: "Cristhian", age: 26)
onPersonChanged?(person)
}
}
This means, the ViewController
needs to set that block we mentioned above, so when the person’s information changes, the view can “refresh” the UI component values.
The function refreshUI
simply takes a person and sets the values to the labels. On the other hand, the addObservers
function sets the callback for when the person data updates.
Thus far, this implementation has nothing bad at all, however, when the view grows, the refreshUI function will be called even when some values haven’t changed, besides, testing can be complicated.
That’s why my plan now is to single out every property of the viewModel to have full independence and gain testability.
Now you might be wondering — what is that Dynamic
class? Well, this is the whole point of this post. Dynamic
is a generic class that allows us to react to changes in its value. That means, ViewController
can observe changes on a dynamic property of the ViewModel
, then act upon it, but first, let’s see class.
As you can see, it’s a very simple, yet powerful class that executes the blocklistener
whenever the value has been set. Dynamic
also exposes a function called bind
to set that listener. Let’s now see how the ViewController
hooks to the ViewModel
properties.
private func addObservers() {
viewModel.personName.bind { [weak self] name in
self?.nameLabel.text = name
}
viewModel.personAge.bind { [weak self] age in
self?.ageLabel.text = "\(age)"
}
}
Thanks for reading. I hope you have enjoyed this small piece of code, and if it was useful for you, don’t be shy to 👏 on this article. See you next time.