Creating an observable object in SwiftUI
Tags: ios, swift, swiftui, programming
Published: 2020-05-20 14:11:16

---
title: Creating an observable object in SwiftUI
published: true
description: In this tutorial I show you how to create a custom observable object by using a pattern I commonly use in my SwiftUI projects to present alert views.
tags: iOS,swift,swiftui,programming
---

In SwiftUI views are functions of the state. They are also reactive, which means they re-render whenever the state they are bound to changes. For value-type state owned by the view itself, we can use `@State` property wrapper. For complex reference-type objects we need a different approach. To demonstrate that, I present in the following sections a pattern I commonly use in my SwiftUI projects to control the state of alert views. You will need basic understanding of SwiftUI to proceed.

## Designing the view model class

The first step is to create the class that will model the `Alert`. It initially needs a title, a message, buttons and another property to control the alert's visibility:

```swift
final class AlertViewModel: ObservableObject {
    var title: String
    var message: String
    var primaryButton: Alert.Button
    var secondaryButton: Alert.Button?

    @Published var isPresented = false

    init(title: String = "",
         message: String = "",
         primaryButton: Alert.Button = .default(Text("Ok")),
         secondaryButton: Alert.Button? = nil) {
        self.title = title
        self.message = message
        self.primaryButton = primaryButton
        self.secondaryButton = secondaryButton
    }
}
```

The `ViewModel` suffix is because of the architectural pattern I most commonly use, **MVVM** (_Model-View-ViewModel_). If you don't know it, don't worry, just think of `AlertViewModel` as an object that dictates the behavior and appearance of the alert.

Back to the code, notice the conformance with the `ObservableObject` protocol. This is what makes it possible for views to bind to `AlertViewModel` and listen for changes. Also, we have `isPresented` marked with `@Published`, a property wrapper which, in simple terms, publishes changes in the underlying property so any bound views know _when_ to re-render.

And that's basically all we need, but before wiring this up to our views, let's define some helpers to allow us not to repeat ourselves. After all, showing alerts is a fairly common task, right?

## Defining helper functions

You will notice over time that it becomes pretty annoying having to wrap button's label with `Text` when all we need is a simple string button. To overcome that, let's start by defining some extension functions to `Alert.Button`:

```swift
extension Alert.Button {
    static func `default`(_ label: String, action: (() -> Void)? = nil) -> Alert.Button {
        .default(Text(label), action: action)
    }

    static func destructive(_ label: String, action: (() -> Void)? = nil) -> Alert.Button {
        .destructive(Text(label), action: action)
    }
}
```

This code allows us to pass in a `String` instead of a `Text`. Now we can update our `AlertViewModel`'s initializer to use `.default("Ok")`.

Next, if you take a look at `Alert`'s initializers, you will notice that not all of them receive a primary and secondary buttons. So, instead of handling this on a case-by-case basis, we can extend `Alert` by providing a custom initializer that takes `AlertViewModel` as a parameter:

```swift
extension Alert {
    init(viewModel: AlertViewModel) {
        if let secondaryButton = viewModel.secondaryButton {
            self.init(title: Text(viewModel.title),
                      message: Text(viewModel.message),
                      primaryButton: viewModel.primaryButton,
                      secondaryButton: secondaryButton)
        } else {
            self.init(title: Text(viewModel.title),
                      message: Text(viewModel.message),
                      dismissButton: viewModel.primaryButton)
        }
    }
}
```

The initializer works by simply checking whether or not the view model provides a secondary button and dispatching to the proper built-in initializer based on that. Now we have all pieces we needed, so let's glue them all together.

## Presenting the alert

Place the following in your content view:

```swift
struct ContentView: View {
    @ObservedObject private var alertViewModel = AlertViewModel()

    // 4
    private func presentErrorAlert() {
        alertViewModel.title = "Sorry :("
        alertViewModel.message = "Could not fetch your data, please try again later"
        alertViewModel.primaryButton = .default("Ok") {
            print("Closed error alert")
        }
        alertViewModel.secondaryButton = .destructive("🔥") {
            print("Some destructive action")
        }
        alertViewModel.isPresented = true
    }

    var body: some View {
        // 1
        Button("Present alert") {
            self.presentErrorAlert()
        }

        // 2
        .alert(isPresented: $alertViewModel.isPresented) {
            // 3
            Alert(viewModel: self.alertViewModel)
        }
    }
}
```

Let's break the code above and understand what's going on:

1. We add a `Button` that triggers `presentErrorAlert()` function when tapped.

2. We add the `alert` modifier to the button. The first parameter, `$alertViewModel.isPresented`, is a `Binding` to the `isPresented` property from the view model. A binding is a very powerful feature of SwiftUI, you can think of it as a _read-write pipe_ that allows reading and writing values to it. Thus, when `isPresented` becomes `true`, view's body re-renders and the alert displays. When we tap to dismiss the alert, the modifier writes `false` to `isPresented` and the view re-renders once again, causing the alert to disappear. Wow, that's a lot for a single line of code!

3. As the second parameter for the alert modifier we have a closure which returns the actual `Alert` that should be displayed. For that we use the initializer we defined in the previous section.

4. This function sets the values that describe how the alert will look like. We define title, message and the companion buttons. Finally, we set `isPresented` to publish the changes so the view knows it should re-render.

And that's it! Check out the result:

![Alert GIF](https://media.giphy.com/media/l57mDRIpzf7TT05rRC/giphy.gif)


## Conclusion

If it felt like a lot to accomplish such a simple task, don't worry, it will feel more natural and simpler over time once you internalize SwiftUI's approach of doing things.

In addition, there are a couple of improvements we could perform to harness the power of SwiftUI. I decided to keep them out for the sake of simplicity and to not lose focus on the subject. But, if you have some time, try looking into how to use `LocalizedStringKey` (rather than `String`) and also how to create a custom view modifier to pass in the view model directly.

I hope you enjoyed this!