SwiftUI's declarative syntax makes it straightforward to describe the user interface you have in mind. The API is intuitive and the framework's learning curve is gentle. But Apple didn't stop there. SwiftUI is deeply integrated into Xcode, making it almost trivial to build user interfaces using SwiftUI.

Xcode Previews

Revisit the Notes project and open ContentView.swift. Xcode automatically shows you a preview of the view you are working on in the canvas on the right. Xcode generates the preview by building your application behind the scenes. Xcode makes a number of optimizations to make this process as fast as possible. It uses the current run destination to create the preview, iPhone 12 in this example. You learn how to change that later in this episode.

Because Xcode is aware that we are currently working on ContentView, it knows that it only needs to compile ContentView.swift to update the preview. Every time we make a change in the code editor on the left, Xcode updates the preview in the canvas on the right. If you make significant code changes, Xcode pauses the preview. You can resume the preview by clicking the Resume button in the top right or by pressing Command + Option + P. Remember that keyboard shortcut because you use it frequently when building user interfaces using SwiftUI.

Previews are convenient and have several benefits. First, you don't need to run the application in a simulator or on a physical device every time you make a change. This allows for a much shorter feedback cycle, enabling rapid user interface development.

Second, you don't need to navigate to the view your are working on. That one benefit can save you a lot of time. It also means you don't need to modify the application's state to preview the changes you made.

Third, it is trivial to configure the preview however you like. Let's find out what ContentView looks like if we pass a long string to ContentView's initializer.

Working with Xcode Previews

SwiftUI's deep integration with Xcode may seem like a nice to have feature, but I can assure you that it has a noticeable impact on your day to day work. Because of the much shorter feedback cycle, user interface development is faster and less frustrating. You are also more likely to spot user interface issues you might otherwise overlook.

Creating a Preview

To create a preview, you declare a type that conforms to the PreviewProvider protocol. A type that conforms to PreviewProvider is required to implement a static computed previews property of type some View. The view the computed previews property returns is rendered by Xcode in the canvas on the right.

The PreviewProvider protocol is defined in the SwiftUI framework, which means you can take advantage of the the framework's declarative syntax to configure the previews you create. The current run destination defines the preview on the right, iPhone 12 in this example. We don't need to change the run destination every time we want to preview the view on a different device. To change the device the preview renders, we invoke the previewDevice(_:) method on the ContentView instance, passing in a PreviewDevice instance.

The initializer of PreviewDevice accepts the name of the device as an argument. That name needs to match the name of one of the simulators listed in the run destination menu at the top. Let's change it to iPhone 8.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
            .previewDevice(PreviewDevice(rawValue: "iPhone 8"))
    }
}

Previewing the User Interface on iPhone 8

Changing the Configuration

What does ContentView look like if the user has dynamic type enabled? Without previews, you would need to change a system setting and run the application to answer that question. Previews make this trivial. Because the previewDevice(_:) method returns a view we can use method chaining to further configure the preview. We invoke the environment(_:_:) method on the result returned by previewDevice(_:), passing in the key path of an environment value as the first argument and the value for the environment value as the second argument. The environment(_:_:) method allows us to configure the environment of the preview.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
            .previewDevice(PreviewDevice(rawValue: "iPhone 8"))
            .environment(\.sizeCategory, .extraSmall)
    }
}

Xcode updates the preview, taking the updated environment into account. We discuss the environment and environment values in more detail later in this series.

Multiple Previews

Most applications support multiple platforms, but it can be tedious to inspect the user interface on the various devices your application supports. Previews make that tedious task a thing of the past. It is trivial to create multiple previews, each with a different configuration. Let me show you how that works.

We embed the preview in a group by pressing Command, clicking the ContentView instance, and choosing Group from the contextual menu. To add another preview to the canvas on the right, we simply create another ContentView instance. This time we pass iPhone 12 to the initializer of PreviewDevice.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
                .previewDevice(PreviewDevice(rawValue: "iPhone 8"))
                .environment(\.sizeCategory, .extraSmall)
            ContentView(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
                .previewDevice(PreviewDevice(rawValue: "iPhone 12"))
                .environment(\.sizeCategory, .extraSmall)
        }
    }
}

Notice that Xcode now displays two previews in the canvas on the right.

Using Multiple Xcode Previews

We can configure the environment for each preview separately. Let's increase the text size by passing extraLarge as the second argument of the environment(_:_:) method.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
                .previewDevice(PreviewDevice(rawValue: "iPhone 8"))
                .environment(\.sizeCategory, .extraSmall)
            ContentView(title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
                .previewDevice(PreviewDevice(rawValue: "iPhone 12"))
                .environment(\.sizeCategory, .extraLarge)
        }
    }
}

The possibilities are endless. Xcode previews offer you the ability to preview the user interface you are building, saving time and avoiding frustration.

Editing the User Interface

The previews Xcode generates are more than previews. You can edit the user interface of a view using the editor on the left, the code editor, but it is also possible to make changes to the preview on the right, the visual editor. Remove the second preview and select the bottom Text instance. Notice that Xcode highlights the Text instance in the code editor. This subtle hint is very helpful if you are still learning about the SwiftUI framework.

Press Command and click the bottom Text instance in the visual editor. The contextual menu shows you a number of options. Choose Show SwiftUI Inspector.... The SwiftUI inspector shows you the options you have to configure the Text instance. Change the font of the Text instance by setting Font to Title. Xcode updates the preview in the code editor on the left and the visual editor on the right to reflect the change you made.

Using the Visual Editor in Xcode

As I mentioned earlier, this is very helpful to learn the SwiftUI syntax and to find out what options you have to configure a view. As you become more familiar with SwiftUI, you make most of your changes in the code editor. The SwiftUI inspector is very helpful if you are new to SwiftUI or if you are exploring an API you are less familiar with.

Note that you can also Command click the Text instance in the code editor. The same contextual menu appears, allowing you to configure the Text instance. Remember that Xcode simply translates your selection into code. This means that you can freely edit the code Xcode added. Let's change the font of the Text instance to body in the code editor. Xcode updates the preview on the right to reflect the change.

What's Next?

Xcode previews are more than the cherry on the cake. They are incredibly powerful and an indispensable instrument to build user interfaces using SwiftUI. We only scratched the surface in this episode. You learn more about Xcode previews in this series.

In the next episode, we focus on another important concept of the SwiftUI framework, modifiers. The font(_:) method we invoked on the Text instance is a modifier. Views and modifiers work in tandem to build user interfaces using SwiftUI.