Implement Two-Way UIKit Binding in Vanilla Swift
Avoid third parties if you can!
Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 12.1, and Swift 5.3, and certain features (\String.self keypath) require Swift 5+
If you would just like to use the library, head over to https://github.com/stevencurtis/TwoWayBindingUIKit and use Swift Package Manager to install it!
- You will be expected to be aware how to make a Single View Application in Swift
- I have taken a programmatic approach to the interface, although this is unlikely to trip you up (the guide is here)
- Keypaths are a major factor in the ultimate solution in this article
- If you’d like to take a look at the theory of the Mediator Pattern that might give you a hand
- There is some mention of optionals
- We have some Generics in Swift
- You would also need some knowledge about extensions and protocols
MVVM is a fantastic Architecture pattern for iOS. However, if you do not implement the two-way binding you are likely to end up with the subtly different MVP architecture.
The answer? Use two-way bindings. One way to do this is to add RxSwift or other dependencies into your code. Don’t want to add those third-party libraries? Read on to find out how to avoid bloating your code for these bindings.
Binding: A technique that binds data sources from the provider and consumer together and synchronizes them MVVM: An architecture pattern consisiting of Model, View and View-Model Observable: An emitter of notifications of a change Observer: An object that wishes to be notified when the state of another object changes. Subscribe: An observer lets a subject know that it wants to be informed of changes through a process called subscribing Subscriber: A term meaning Observer Type: A representation of the tyoe of data that can be processed, for example Integer or String
The open-closed principle says that implementation should hide behind an interface — that is; loose coupling.
Two-way binding means that we can connect the View to the View Model. The idea is that the View and the View Model are held in sync.
Since the View Model mediates in between the view and the model it no only updates the view, but updates it. That is, two-way (bi-directional) binding, in the style of The Mediator Pattern.
Observables are at the heart of reactive programming (although they do not cover the entire range of reactive programming as the principes at work go beyond just this. Effectively this functionality is inbuilt in RxSwift however our own implementation can avoid needing to import that rather large third-party library!
I’m going to create a rather dull project that has three
UITextField components on it, each of which will implement two-way binding in slightly different ways. This finished project looks just like the following image:
Now of course since these are
UITextField components you can download the repo, take a look and change what they say!
Now this particular project does not use storyboards as I have opted for a programmatic approach.
The first UITextField Observing a String, AKA one-way binding fixed to be two-way
firstTF is connected to a
String in the
ViewModel, but in this case we aren't going to use a simple
String - this is going to be an Observable - that is, a type that can be observed, and I've constructed this as a class.
We can then observe any changes that are made from the
Now we need to keep the original value in the
ViewModel synchronised. To do so we can set the delegate of the text field, and update the value in the view model.
Observable class looks like the following:
essentially we take an optional
value (so this works with optional type). Essentially we assign the value during initialization, and when we observe the value the observer is added to an array of observers, and initially (and when the value is changed) the completion handler is called.
In the code in the repo you’ll see that I’ve used a delay with
DispatchQueue.main.asyncAfter - something I would not do in production code. No, a better way of testing code is to use testing:
One difficulty with this approach is that when we type a change in the
firstTF text field the
UITextField will be updated twice (so there would be a performance penalty for using this approach, which (to be honest) is not two-way binding at all as we have to perform extra work in the delegate function of
We therefore need to find a better approach!
The second UITextField
secondTF uses a bind function (which is actually a two-way binding) as:
which once again connects to an
Bindable itself is a protocol:
which any specific
UIControl conforms to - in this case we are using
UITextField as an example. This uses the
Observable class as defined in the previous section of this article, and leverages a
UITextFieldDelegate within an extension.
Here is the protocol, and the extension:
Now this can be improved by using the improved version of Stored Properties in this article, but this implementation does indeed work:
So we have avoided having the delegate of the
UITextField in the
UIViewController, but that comes at a real cost - what if we want to use any of these delegate functions in our own code? Well, certainly we could use
NSNotificationCenter but even if we went down that road, there would be another problem.
We’d have to write this extension for each and every control that would be used. This would be, quite clearly, awful. There has to be a better way.
In steps the third example.
The third example
We can use keypaths to make a
MakeBindable type that we will use in place of the
Observable type we used before:
which is then bound using the following:
MakeBindable is a class, where we can optionally add an initial value, that is:
The initializer optionally creates the inititial value (which, as it can change is called
previousValue which would represent the previous value in a stream of values.
the bind function as the entry point to add the observer, using the keypath as chosen using the following:
which itself calls the following private function:
which of course, when updated will call each of these observers:
This all sounds great until we look into the detail. More testing is required!
We have implemented better one-way binding, but haven’t yet implemented two-way binding! Curses!
We will need a function to show us the current value
now when the binding type is a
UIControl I would like to update the value, using a function something like:
Where we run the new value to each of the observers, and update our
In itself this function is created when we bind the values, here is the updated bind function:
which of course relies upon the Keypath property:
Which we can then use, for example we can bind a
UILabel to a property called
This all seems nice and fine. However, we might want to bind different values. To do so we would need to have some sort of mapping functions:
which can then be implemented with the following binding function that functions in a similar way as has been explained above:
Which can then be used (for example to bind a boolean to a label:
The code in the view controller can be simpilfied with the implementation of two-way binding. Therefore the testing code I have added in the repo may be of interest, especially since I’ve added the subclassed
UITextField here, with some small tests:
Now remember: This
MakeBindable class can be used for any controls. Wonderful!
Two-way binding without using
RxSwift or similar? That's interesting as we can see how the functions should work, and we can even have a great new use for
Keeping our Apps small and efficient? That is certainly something that we should be doing as we create better and better Apps. Take a look at the repo, and in particular the second
UIViewController that has examples of a series of controls. I hope this helps you out!
This article even contains testing so you can be sure that this deal with your
⌘U to your hearts content. I hope you have enjoyed this article.
Isn’t that nice? It sure is!
If you’ve any questions, comments or suggestions please hit me up on Twitter