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.
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 .
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
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
ControlPublisher is based on
UIControl events it will never end so we can set our
Failure typealias to
Value is the emitted
Value for the
There are some stored properties, 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 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)and
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 the
UIControland sets the subscriber to nil.
initsets private properties and add the Subscription as a target on the control for the given event.
handleEvent()is called when the
Control.Eventhappens and calls
receiveon the subscriber.
ControlSubscription is finished, our receive function in
ControlPublisher creates an instance of
ControlSubscription and provides it to our
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.
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 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.
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.
viewDidLoad we setup our bindings with whatever view model we have.
For us to test our view model API, we just need a way to easily create publishers. Combines
CurrentValueSubjects can be used.
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.
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