Implement Two-Way UIKit Binding in Vanilla Swift

Avoid third parties if you can!

Image for post
Image for post
Photo by Matthew Henry

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!

Prerequisites:

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.

Image for post
Image for post

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.

Image for post
Image for post

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:

Image for post
Image for post
The completed project

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 UITextField called 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 ViewModel

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.

So that 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:

I did use DispatchGroups in the full tests in the Repo so please do try to take a look there if you want the full details!

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 UITextField.

We therefore need to find a better approach!

The secondTF uses a bind function (which is actually a two-way binding) as:

which once again connects to an Observable:

Now 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…

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.

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:

So MakeBindable is a class, where we can optionally add an initial value, that is:

with functions that use generics to bind AnyObject to specific keypaths.

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:

Further testing

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!

Improving the MakeBindable class

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 previousValue.

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 value:

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:

Finishing up

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!

Conclusion

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 KeyPaths.

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

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store