Extending UIKit with Combine, MVVM and Unit Tests
Simple Combine Publisher & Subscription for UIKit’s UITextField with MVVM and Unit Tests.
In a previous article I wrote about getting to know ReactiveCocoa 5, with MVVM and Unit Tests. The premise was a pretty basic login screen and provided a good jumping off point to see some uses of a reactive library.
Here we’ll take a similar approach, but with Combine and UIKit. While building a screen like this in SwiftUI is easy, you may already have an existing UIKit app that you want to introduce Combine too. Or even start replacing some RxSwift or ReactiveCocoa.
ReactiveCocoa provides continuousTextValues
for any UITextField
. You can couple this value to other UI updates to create a UI that provides plenty of feedback to the end user. Combine doesn’t provide us with such a function. But that provides us with a chance to write our own Publisher and Subscription .
Architecture
The application has a simple MVVM-C architecture. A Coordinator will create the LoginViewController
and can update it with a LoginViewModel
. The view model will handle any UI or display-logic and the Coordinator
will handle any navigation.
Instead of using the delegate callbacks to know when text is changing in a UITextField
, we create a custom publisher for the UIControl
events and and subscribe to this publisher in our LoginViewController
.
ControlPublisher
Let’s start with the ControlPublisher
. This isn’t as scary as it seems.
A custom publisher needs to implement the receive(subscriber: s)
function and define an Output
and a Failure
.
Since ControlPublisher
is based on UIControl
events it will never end so we can set our Failure
typealias to Never
.
Value
is the emitted Value
for the KeyPath
provided.
There are some stored properties, the UIControl
, the KeyPath
whose value will be emitted, and the events we want to emit them on.
In order to handle the receive(subscriber: s)
function, we need a Subscription.
ControlSubscription
ControlSubscription
is a little more complicated. In the Combine world it’s important to remember that the Publisher doesn’t hold a reference to a Subscriber or Vice Versa. When a Subscriber subscribes to a Publisher, the Publisher creates a Subscription for that Subscriber, and the Subscription sends new values to the Subscriber.
Let’s break down some of the parts here.
- Our standard Subscription methods are
request(_ demand: Subscribers.Demand)
andcancel()
request(_ demand: Subscribers.Demand)
checks if we have sent the initial value, and if not, sends it to the subscriber.cancel()
removes our Subscription as a target on theUIControl
and sets the subscriber to nil.init
sets private properties and add the Subscription as a target on the control for the given event.handleEvent()
is called when theControl.Event
happens and callsreceive
on the subscriber.
phew.
Now that ControlSubscription
is finished, our receive function in ControlPublisher
creates an instance of ControlSubscription
and provides it to our Subscriber
.
Extending UIKit
An extension to UITextField
with a computed property textPublisher
is created. Similar to the UITextField
text property, it’s a publisher with an optional String value and Never ends.
LoginViewModel
Any view controller logic is here. i.e. when a password doesn’t satisfy a certain length, the password UTextField
should have a red background color & only when the password and username field passes validation should we display the login button.
LoginViewController
LoginViewController
is basic — just the way we like it. We set up the subviews and lay them out with anchors.
There is a viewModel
property and a subscriptions Set<AnyCancellable>
that stores any subscriptions the view controller is subscribed to.
Since our UITextField
properties now have a Combine based publisher, we don’t need to add targets or special handling of their text values. The publishers are sent to our view model the subscription is stored in our view controller.
In viewDidLoad
we setup our bindings with whatever view model we have.
Testing
For us to test our view model API, we just need a way to easily create publishers. Combines CurrentValueSubjects
can be used.
Closing
Thanks for taking the time to read this article. Hopefully it helps you understand some of how Combine works the same way writing this helped me! I hope it enables you to bring these reactive programming techniques to your app.
Further Resources
The code featured here are parts of a sample project. The sample app has been extended with a button ControlEvent
that is similar to ControlPublisher
but doesn’t take a KeyPath
.