SwiftUI + Combine = ❤️
Tags: swiftui, combine, ios, frp
Published: 2019-11-04 14:48:49

---
title: SwiftUI + Combine = ❤️
published: true
date: 2019-09-13 07:45:56 UTC
tags: SwiftUI, Combine, iOS, FRP
canonical_url: https://peterfriese.dev/swift-combine-love/
---

_Why SwiftUI and Combine will help you to build better apps_

One of the biggest announcements at WWDC 2019 was SwiftUI - its declarative approach makes building UIs a breeze, and it’s easy to see why people are so excited about it. The hidden gem, however, was the Combine framework, which didn’t get as much fanfare as I think it deserves.

In this article, we will take a closer look at how to use SwiftUI and Combine together, build better apps, and have more fun along the way.

Ready? Let’s go!

## What you are going to learn

- What the Combine framework is, and how to integrate it with SwiftUI
- What publishers, subscribers, and operators are, and how to use them
- How to organise your code

## In the Beginning…

To help us reason about SwiftUI and Combine, we’re going to use a simple sign-up screen which lets users enter a username and password to create a new account in an application. In a later article, we will add a login screen to demonstrate some of the additional benefits of using Combine.

The data model for the sign-up screen is simple enough:

- Users need to enter their desired username
- They also need to pick a password

The requirements for username and password are pretty straight forward:

- The username must contain at least 3 characters
- The password must be non-empty and strong enough
- Also, to make sure the user didn’t accidentally mistype, they need to type their password a second time, and both of these passwords need to match up

Let’s put this down in code!

I’ve decided to use an MVVM architecture - this results in a clean code base and will make it easier to add new functionality to the app. First, let’s define the _ViewModel_ which has a couple of properties taking the user’s input (such as the username and passwords), and - for the time being - a property to expose the result of any business logic we’re going to implement shortly.

``` swift
class UserViewModel: ObservableObject {
  // Input
  @Published var username = ""
  @Published var password = ""
  @Published var passwordAgain = ""

  // Output
  @Published var isValid = false
}
```

For the sign-up screen, we use a `Form` with several `Section`s for the various input fields, which gives us a clean look and feel. It gets the job done, but doesn’t look very exciting. In the next episode, we’re going to brush it up to demonstrate how SwiftUI and Combine make it possible to make changes to your UI without having to modify the underlying business logic.

``` swift
struct ContentView: View {

  @ObservedObject private var userViewModel = UserViewModel()

  var body: some View {
    Form {
      Section {
        TextField("Username", text: $userViewModel.username)
          .autocapitalization(.none)
        }
        Section {
          SecureField("Password", text: $userViewModel.password)
          SecureField("Password again", text: $userViewModel.passwordAgain)
       }
       Section {
         Button(action: { }) {
           Text("Sign up")
         }.disabled(!userViewModel.valid)
       }
     }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
```

Notice how we’re using SwiftUI bindings to access the properties of our view model. The _Sign up_ button is bound to the `isValid` output property of the view model. As this defaults to `false`, the button is initially disabled, which is what we want - after all, the user shouldn’t be able to create an account with an empty username and password!

This is how the UI looks so far:

[![screenshot dark2](https://peterfriese.dev/static/62350705f5718a62ec5ee4b30967d0f8/b9e4f/screenshot-dark2.png "screenshot dark2")](/static/62350705f5718a62ec5ee4b30967d0f8/af534/screenshot-dark2.png)

## Introducing Combine

Before implementing the validation logic for our sign-up form, let’s spend some time understanding how the Combine framework works.

According to the Apple documentation:

> The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers. ([source](https://developer.apple.com/documentation/combine))

Let’s take a closer look at a couple of key concepts here to understand what this means and how it helps us.

### Publishers

Publishers send values to one or more subscribers. They conform to the `Publisher` protocol, and declare the type of output and any error they produce:

``` swift
public protocol Publisher {
  associatedtype Output
  associatedtype Failure : Error
  func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
```

A publisher can send any number of values over time, or fail with an error. The associated type `Output` defines which kinds of values a publisher can send, whereas the associated type `Failure` defines the type of error it may fail with. A publisher can declare it never fails by specifying the `Never` associated type.

### Subscribers

Subscribers, on the other hand, subscribe to one specific publisher instance, and receive a stream of values, until the subscription is cancelled.

They conform to the `Subscriber` protocol. In order to subscribe to a publisher, the subscriber’s associated `Input` and `Failure` types must conform to the publisher’s associated `Output` and `Failure` types.

``` swift
public protocol Subscriber : CustomCombineIdentifierConvertible {
  associatedtype Input
  associatedtype Failure : Error

  func receive(subscription: Subscription)
  func receive(_ input: Self.Input) -> Subscribers.Demand
  func receive(completion: Subscribers.Completion<Self.Failure>)
}
```

### Operators

Publishers and subscribers are the backbone of SwiftUI’s two-way synchronisation between the UI and the underlying model. I think you will agree that it has never been easier to keep your UI and model in sync than with SwiftUI, and this is all thanks to this part of the Combine framework.

Operators, however, are Combine’s superpower. They are methods that operate on a `Publisher`, perform some computation, and produce another `Publisher` in return.

- For example, you can use the `filter` operator to ignore values based on certain conditions.
- Or, if you need to perform an expensive task (such as fetching information across the network), you could use the `debounce` operator to wait until the user stops typing
- The `map` operator allows you to transform input values of a certain type into output values of a different type

## Validating the Username

With this in mind, let’s implement a simple validation to make sure the user entered a name that has at least three characters.

All properties on our view model are wrapped with the `@Published` property wrapper. This means each property has its own publisher, which we can subscribe to.

To indicate whether a username is valid, we transform the user’s input from `String` to `Bool` using the `map` operator:

``` swift
$username
  .debounce(for: 0.8, scheduler: RunLoop.main)
  .removeDuplicates()
  .map { input in
    return input.count >= 3
  }
  .assign(to: \.valid, on: self)
  .store(in: &cancellableSet)
```

The result of this transformation is then consumed by the `assign` subscriber, which - as the name implies - assigns the received value to the `valid` output property of our view model.

Thanks to the binding we configured earlier in `ContentView.swift`, SwiftUI will automatically update the UI whenever this property changes. We will later see why this approach is a bit problematic, but for now, it works just fine.

You might wonder what’s this fancy business with the `debounce` and `removeDuplicate` operators? Well, these are part of what makes Combine such a useful tool for connecting UIs to the underlying business logic. In all user interfaces, we have to deal with the fact that the user might type faster than we can fetch the information they’re requesting. For example, when typing their username, it is not necessary to check whether the username is valid or available for every single letter the user types. It is sufficient to perform this check only once they stop typing (or pause for a moment).

The `debounce` operator lets us specify that we want to wait for a pause in the delivery of events, for example when the user stops typing.

Similarly, the `removeDuplicates` operator will publish events only if they are different from any previous events. For example, if the user first types `john`, then `joe`, and then `john` again, we will only receive `john` once. This helps make our UI work more efficiently.

The result of this chain of calls is a `Cancellable`, which we can use to cancel processing if required (useful for longer-running chains). We’ll store this (and all the others that we will create later on) into a `Set<AnyCancellable>`, so we can clean up upon `deinit`.

## Validating the Password(s)

Let’s now switch gears and look into how we can perform multi-staged validation logic. This is required as the password fields on our form need to meet multiple requirements: they must not be empty, they must match up, and (most importantly) the chosen password must be strong enough. In addition to transforming the input values into a `Bool` to indicate whether the passwords meet our requirements, we also want to provide some guidance for the user by returning an appropriate warning message.

Let’s take this one step at a time and begin by implementing the pipeline for validating the passwords the user entered.

``` swift
private var isPasswordEmptyPublisher: AnyPublisher<Bool, Never> {
    // (1)
    $password
      .debounce(for: 0.8, scheduler: RunLoop.main)
      .removeDuplicates()
      .map { password in
        return password == ""
      }
      .eraseToAnyPublisher()
  }
```

Checking whether the password is empty is pretty straightforward, and you will notice this method is very similar to our implementation of the username validation. However, instead of directly assigning the result of the transformation to the `isValid` output property, we return an `AnyPublisher<Bool, Never>`. This is so we can later combine multiple publishers into a multi-stage chain before we subscribe to the final result (valid or not).

To verify if two separate properties contain equal strings, we make use of the `CombineLatest` operator. Remember that the properties bound to the respective `SecureField` fire each time the user enters a character, and we want to compare the _latest_ value for each of those fields. `CombineLatest` lets us do that.

``` swift
private var arePasswordsEqualPublisher: AnyPublisher<Bool, Never> {
    Publishers.CombineLatest($password, $passwordAgain)
      .debounce(for: 0.2, scheduler: RunLoop.main)
      .map { password, passwordAgain in
        return password == passwordAgain
      }
      .eraseToAnyPublisher()
  }
```

To compute the password strength, we use [Navajo Swift](https://github.com/jasonnam/Navajo-Swift), a Swift port of [https://twitter.com/mattt?lang=en](https://twitter.com/mattt?lang=en) excellent [Navajo](https://github.com/mattt/Navajo) library (2)., and convert the resulting enum into a `Bool` by chaining on another publisher (`isPasswordStrongEnoughPublisher`). This is the first time we subscribe to one of our own publishers, and very nicely shows how we can combine multiple publishers to produce the required output.

``` swift
private var passwordStrengthPublisher: AnyPublisher<PasswordStrength, Never> {
    $password
      .debounce(for: 0.2, scheduler: RunLoop.main)
      .removeDuplicates()
      .map { input in
        return Navajo.strength(ofPassword: input) // (2)
      }
      .eraseToAnyPublisher()
  }
  
  private var isPasswordStrongEnoughPublisher: AnyPublisher<Bool, Never> {
    passwordStrengthPublisher
      .map { strength in
        switch strength {
        case .reasonable, .strong, .veryStrong:
          return true
        default:
          return false
        }
      }
      .eraseToAnyPublisher()
  }
```

In case you’re wondering why we need to call `eraseToAnyPublisher()` at the end of each chain: this performs some type erasure that makes sure we don’t end up with some crazy nested return types.

[![we need to go deeper](https://peterfriese.netlify.com/static/509087384ca5b95b704aa26c9da81be0/b9e4f/we-need-to-go-deeper.png "we need to go deeper")](/static/509087384ca5b95b704aa26c9da81be0/34e8a/we-need-to-go-deeper.png)

Great - so we now know a lot about the passwords the user entered, let’s boil this down to the single thing we really want to know: _is this a valid password_?

As you might have guessed, we will need to use the `CombineLatest` operator, but as this time around we have three parameters, we’ll use `CombineLatest3`, which takes three input parameters.

``` swift
enum PasswordCheck {
    case valid
    case empty
    case noMatch
    case notStrongEnough
  }
  
  private var isPasswordValidPublisher: AnyPublisher<PasswordCheck, Never> {
    Publishers.CombineLatest3(isPasswordEmptyPublisher, arePasswordsEqualPublisher, isPasswordStrongEnoughPublisher)
      .map { passwordIsEmpty, passwordsAreEqual, passwordIsStrongEnough in
        if (passwordIsEmpty) {
          return .empty
        }
        else if (!passwordsAreEqual) {
          return .noMatch
        }
        else if (!passwordIsStrongEnough) {
          return .notStrongEnough
        }
        else {
          return .valid
        }
      }
      .eraseToAnyPublisher()
  }
```

The main reason why we map the three booleans to a single enum is that we want to be able to produce a suitable warning message depending on the result of the validation. Telling the user that their password is no good is not very helpful, is it? Much better if we tell them _why_ it’s not valid.

## Putting it All Together

To compute the final result of the validation, we need to combine the result of the username validation with the result of the password validation. However, before we can do this, we need to refactor the username validation so it also returns a publisher that we include in our validation chain.

``` swift
private var isUsernameValidPublisher: AnyPublisher<Bool, Never> {
    $username
      .debounce(for: 0.8, scheduler: RunLoop.main)
      .removeDuplicates()
      .map { input in
        return input.count >= 3
      }
      .eraseToAnyPublisher()
  }
```

With this done, we can implement the final stage of the form validation:

``` swift
private var isFormValidPublisher: AnyPublisher<Bool, Never> {
    Publishers.CombineLatest(isUsernameValidPublisher, isPasswordValidPublisher)
      .map { usernameIsValid, passwordIsValid in
        return usernameIsValid && (passwordIsValid == .valid)
      }
    .eraseToAnyPublisher()
  }
```

By now, this should look rather familiar to you.

## Updating the UI

None of this would be very useful without connecting it to the UI. To drive the state of the _Sign up_ button, we need to update the `isValid` output property on our view model.

To do so, we simply subscribe to the `isFormValidPublisher` and assign the values it publishes to the `isValid` property:

``` swift
init() {
    isFormValidPublisher
      .receive(on: RunLoop.main)
      .assign(to: \.isValid, on: self)
      .store(in: &cancellableSet)
  }
```

As this code interfaces with the UI, it needs to run on the UI thread. We can tell SwiftUI to execute this code on the UI thread by calling `receive(on: RunLoop.main)`.

Let’s finish off with binding the warning message output properties to the UI to help guide the user through filling out the sign-up form.

First, we subscribe to the respective publishers to learn when the `username` vs. `password` properties are invalid. Again, we need to make sure this happens on the UI thread, so we’ll call `receive(on:)` and pass the main run loop.

``` swift
init() {
    // ...
    isUsernameValidPublisher
      .receive(on: RunLoop.main)
      .map { valid in
        valid ? "" : "username must at leat have 3 characters"
      }
      .assign(to: \.usernameMessage, on: self)
      .store(in: &cancellableSet)
    
    isPasswordValidPublisher
      .receive(on: RunLoop.main)
      .map { passwordCheck in
        switch passwordCheck {
        case .empty:
          return "Password must not be empty"
        case .noMatch:
          return "Passwords don't match"
        case .notStrongEnough:
          return "Password not strong enough"
        default:
          return ""
        }
      }
      .assign(to: \.passwordMessage, on: self)
      .store(in: &cancellableSet)
}
```

Finally, we need to bind the output properties `usernameMessage` and `passwordMessage` to the UI. The section footers are a convenient place to show error messages, and we can make them stand out nicely by colouring them in red:

``` swift
var body: some View {
    Form {
      Section(footer: Text(userModel.usernameMessage).foregroundColor(.red)) {
        // ...
      }
      Section(footer: Text(userModel.passwordMessage).foregroundColor(.red)) {
        // ...
      }
      // ...
    }
  }
```

And here is the result of our hard work in all its glory:

![](https://peterfriese.dev/f036eaf9870f8b70910fca3d7b583e09/demo.gif)

## Conclusion

Building UIs with SwiftUI is a breeze. Apple has sweated the details to give us the tools that make building UIs more productive than ever before. On top of that, SwiftUI follows Apple’s [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) , automatically adapts to dark mode and has accessibility built right in. All of this helps to build better, and more inclusive apps in less time - what’s not to like?

Using Combine results in a cleaner and more modular code that (as we will see in the next episode) is more maintainable and easier to extend.

Of course, like every new paradigm, there is a learning curve, and it will take some time to get to grips with functional reactive programming. But I am convinced it’s worth the effort. By releasing SwiftUI and Combine, Apple have put their sign of approval on Functional Reactive Programming, and soon it will no longer be a technique that only a few development teams use. We will see more and more learning resources to help people get started. Also (and this has been a bit of a sore point in the latest beta releases of Xcode), tooling will get better over time, helping developers to be more productive.

Now is a great time to get started with SwiftUI and Combine - try using them in one of your next projects to get a head start!

## Where to go from here?

- [Source code for this article](https://github.com/peterfriese/SwiftUI-Combine)
- [Introducing Combine - WWDC 2019 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2019/722/)
- [Combine in Practice - WWDC 2019 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2019/721/)
- [Rotato](https://rotato.xyz/) - create amazing mock-ups