Previewing Stateful SwiftUI Views
Interactive Previews for your SwiftUI views
When building UIs in SwiftUI, we tend to build two kinds of UI components: screens and (reusable) views. Usually, we start by prototyping a screen, which will inevitably result in a Massive ContentView that we then start refactoring into smaller, reusable components.
Let’s assume we’re building a todo list application. Here is how it might look like:
I’ve simplified this, but you can imagine that the code for a todo list application like Apple’s Reminders app looks a bit more complicated (in fact, if you’re curious, check out MakeItSo, a sample project I created to replicate the Reminders app using SwiftUI and Firebase).
One way to simplify this code is to refactor the
List view and extract the row into a separate reusable component:
TodoRowView is now a child of the
List view, we want
TodoListView to be the owner of the data. To make sure any changes the user makes (by clicking on the toggle inside the
TodoRowView get reflected on the
List (and vice versa), we need to set up a bi-directional data binding. The right property wrapper for this job is
@Binding - it lets us connect to data that is owned by another view.
However, when trying to set up the
PreviewProvider for this view, we quickly run into some limitations: how can we set up the preview with some demo data so that it can be edited inside the
We might start by creating a static variable on the
PreviewProvider, and then pass it to the view we want to preview. However, this will inevitably result in a compiler error, as
todo to be a
Marking the static
todo variable as
@State resolves the compiler error, but it doesn’t result in an interactive preview:
The go-to solution
The usual way to solve this is to use a constant binding. Apple provides us with a static function on
Binding that makes this straightforward:
As the documentation states, this creates a binding with an immutable value, which prevents the underlying property from being updated, so our implementation might seem broken when we run it in the preview pane.
Using a custom binding
Another strategy for dealing with this situation is to use a custom binding. Here is a static function
mock that stores a value in a local variable and provides read/write access to it.
The nice thing about this technique is that is allows us to replace any calls to
.constant with a call to
However, this solution only works partially. While it is now possible to flip the toggle, the rest of the UI doesn’t update: when a todo is marked as complete, its title should be striked through, but as you can see in the animation, this doesn’t work.
The Real Solution
It helps to remind ourselves that we have full control over the preview - for example, adding
.preferredColorScheme(.dark) to a view inside the
PreviewProvider will turn on dark mode.
With this in mind, the solution becomes obvious: wrap the view inside a container view, and hold the state in this container view:
This is the solution recommended by Apple in their WWDC 2020 session Structure your app for SwiftUI previews (see this timestamp). Since the container view owns the data via the
@State property wrapper, this approach gives us a SwiftUI preview that is fully interactive and responds to any changes of the view’s state.
One more thing
We can even make this easier by creating a generic container view that passes a binding to a value to its contained view:
This allows us to implement a stateful preview with just a few lines of code:
Here is a gist with the code for the
StatefulPreviewContainer, including a sample for how to use it.
SwiftUI previews are a great tool for developing SwiftUI views, and when they work, they provide an amazing developer experience: no need to re-run the application for every little change you make. This significantly reduces turn-around times, and results in a much more efficient workflow.
It feels a bit like an oversight that Apple didn’t provide an easy way to preview views that connect with their host views view
@Binding, but fortunately, there are some ways around this shortcoming.
In this article, I showed you a couple of approaches that you can use when your SwiftUI views make use of
@Binding to communicate with the outside world. Personally, I like the preview container view the most, and you can even make this more efficient by defining an Xcode code snippet.