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 .

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.

MVVM-C

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.

Let’s start with the ControlPublisher. This isn’t as scary as it seems.

Basic ControlPublisher

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

ControlSubscription

Let’s break down some of the parts here.

  • Our standard Subscription methods are request(_ demand: Subscribers.Demand) and cancel()
  • 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 UIControl 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 the Control.Event happens and calls receive 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 .

receive method in ControlPublisher
UITextView Extension using ControlPublisher

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.

Snippet of the LoginViewModel

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.

setBindings snippet in LoginViewController

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

LoginViewModelTests snippet

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

A surfer and a swimmer making his way as an iOS Developer.