Skip to content

Recommended Reading

Boost Your SwiftUI Workflow with Live Previews in Xcode

18 Minutes

Boost Your SwiftUI Workflow with Live Previews in Xcode

Fix Bugs Faster! Log Collection Made Easy

Get started

Introduction

Introduced by Apple in 2019, SwiftUI is a user interface toolkit that provides some radical improvements to how developers built apps for iOS, macOS, watchOS and tvOS. One of its most impressive features is SwiftUI Preview, which lets developers visualize real-time Xcode updates to their UI design. SwiftUI Preview gives instant visual feedback, enhancing the development process as it helps developers quickly iterate and refine their interface.

In this post, we will take a deep dive into what SwiftUI Previews are and explore their key benefits and features. We’ll then look at how to use them, followed by some advanced tricks and best practices to help you get the most out of SwiftUI Previews.

This guide is drafted to help your development work, discussing the best practices for building quality user interfaces in a fast and efficient manner.

Prerequisites

Required tools and libraries: Before getting into SwiftUI Previews, you need the following library and tools:

  1. The latest Xcode, which has SwiftUI and the new Preview. It can be downloaded from the Mac App Store or directly from the Apple Developer website.
  2. SwiftUI is a framework, provided by Apple and included with Xcode, that can be used to build Declarative User Interfaces.
  3. Start by ensuring that your macOS version is the latest to have all dependencies ready for running Xcode with SwiftUI.

Basic Knowledge

It’s best that you already understand the following to get the most out of this guide:

Proficiency in Swift: If you are new to the Swift programming language, learn some of its basics, including syntax and common structures, as this will help you understand many code examples and concepts.

Introduction to SwiftUI: Having a basic understanding of the fundamental concepts, like views, modifiers and layout structures provided by Apple, will help you understand the examples and techniques shown in this blog post.

With this groundwork in place, you’re now ready to take advantage of the powerful features SwiftUI Preview has to offer, and improve your UI development workflow.

Advantages of Using Previews

Real-Time Feedback: Being able to see UI changes happening in real-time is one of biggest advantages of using SwiftUI Previews. This causes the Live Preview to update as soon as changes are made to your SwiftUI code. In turn, this feedback loop helps with rapidly detecting and fixing problems, adjusting the design of a component, and trying out different layouts or looks without building and running your app.

Rapid Iteration: Previews in SwiftUI make it easy to iterate on designs. You can also easily see the result of your changes in real time, without having to compile and run the project after every change. This accelerates the development cycle, helping you try different ideas quickly and iterate on your design more effectively.

Visual Debugging: UI is hard to debug, but SwiftUI Previews helps a lot. You can tell if something is off – misalignment of layout problems, for example – by simply looking at your UI. You can also try an app out in the interactive preview, thus validating that your UI responds to user interaction as expected.

Multiple Device Previews: SwiftUI Previews lets you view your UI from the perspective of many different devices and screen sizes at the same time. This means your design can be made to fit well across a wide range of iOS devices, including iPhones and iPads with various screen sizes, and even in different orientations. It is important to have this capability for maintaining consistent and user-friendly experiences on all the supported devices.

Dynamic Type & Accessibility: The Dynamic Type & Accessibility adjustments of SwiftUI Previews shows how your app UI looks when different font sizes are selected by the user. This helps make your app available for all users, regardless of their font size preference, and by previewing these settings you can build interfaces that are more inclusive and user-friendly.

SwiftUI Previews bring a lot of benefits to the development process, including real-time feedback and rapid iteration, visual debugging and multiple device previews. These powerful features help deliver faster applications that are responsive by design, and with less effort.

Step-by-Step Guide to Setup SwiftUI Previews

This is a pretty straightforward procedure, and can be completed in a few steps while creating a new Basic SwiftUI Preview. This section will introduce you to the concept of building your first SwiftUI Preview, and help you understand the benefits of doing so.

  1. Create a New SwiftUI Project
    1. In xCode, click on File > New > File
    2. Choose template: Select the App template under the iOS section, then click Next.
    3. Give your project a name: Type the name of your project (example TestingSwiftUIApp), pick and choose what you want, then click Next.
    4. Save your project: Select a place to save and click on Create

2: Create a SwiftUI View

  1. Creating a new SwiftUI view: File > New > File, from the menu.
  2. Select SwiftUI view: When Xcode allows you to choose a template, select SwiftUI View and click next.
  3. Name the view: Give a name to your view ContentView and click Create.

3: Write Your SwiftUI Code

With the ContentView.swift created, open the above Swift file, then enter your SwiftUI code.

 //Create a very basic view with text label.
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello!")
            .padding()
    }
}

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

4: Open the Preview Canvas: Click on the Resume button in the upper-right corner of the Xcode window or hit CMD + Option + P. A new window will be opened next to your source code editor showing the UI, as described in ContentView structure. The text “Hello, SwiftUI!” is visible with padding.

5: Changes and Edits in Preview

Update Your Code: Replace the previous ContentView.swift in the SwiftUI file.

//Create background with updated text and background color
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Welcome to Previews!")
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
    }
}

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

Live Updates: The app preview canvas updates automatically to reflect your changes in real-time when you are writing your code.

Customizing SwiftUI Previews

Preview Modifiers: SwiftUI Preview provides different modifiers to help change the look and behavior of your previews. These modifiers represent how your UI will look and behave on different devices and screen sizes, and in any environment.

Preview Layouts: The .previewLayout() modifier is designed to let you change how your view appears inside the canvas space. SwiftUI Previews use a layout for the device which is in place by default, but you can change this to whatever makes sense for you.

//How to use .previewLayout()
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(.fixed(width: 300, height: 100))
    }
}

Device Previews: The .previewDevice() modifier is a way of simulating different devices. That way, you can make sure your design fits well with different screen sizes and orientations.

//Example use .previewDevice()
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice("iPhone14Pro")
                .previewDisplayName("iPhone14Pro")
            ContentView()
                .previewDevice("iPadPro(12.9-inch)")
                .previewDisplayName("iPadPro(12.9-inch)")
        }
    }
}

Preview Sizes: The .previewLayout(.sizeThatFits) modifier is used to adjust with inbound content. It’s particularly helpful for views that are designed to resize themselves based on their content.

//Example use .previewLayout()
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(.sizeThatFits)
            .padding()
            .background(Color.gray)
    }
}

Combining Modifiers: Compose PreviewModifiers by using a different combination of preview modifiers to further refine your previews. With this, you get a nice overall picture of how your UI handles different scenarios.

//Example showing the combining of preview modifiers.
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewLayout(.sizeThatFits)
                .padding()
                .background(Color.gray)
                .previewDisplayName("Size_That_Fits")

            ContentView()
                .previewDevice("iPhone14Pro")
                .previewDisplayName("iPhone14Pro")
                .previewLayout(.device)

            ContentView()
                .previewDevice("iPadPro(12.9-inch)")
                .previewDisplayName("iPadPro(12.9-inch)")
                .previewLayout(.device)
        }
    }
}

Advanced Preview Techniques

Previewing Multiple States: You should use SwiftUI Preview to create a bunch of previews for various view states. This can be useful to see how your UI deals with different data inputs and conditions.

//multiple states example handling the different situations
struct ContentView: View {
    var text: String

    var body: some View {
        Text(text)
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(text: "DefaultState")
                .previewDisplayName("DefaultState")

            ContentView(text: "ErrorState")
                .previewDisplayName("ErrorState")
                .background(Color.red)

            ContentView(text: "SuccessState")
                .previewDisplayName("SuccessState")
                .background(Color.green)
        }
    }
}

Dynamic Type and Dark Mode: One great aspect of SwiftUI Previews is that they show the dynamic type with dark mode automatically. This means you can see how your UI reacts to different text sizes and color schemes.

//Example showing dynamic type and DarkMode
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(text: "DynamicType")
                .previewLayout(.sizeThatFits)
                .environment(\\.sizeCategory, .extraSmall)
                .previewDisplayName("ExtraSmallText")

            ContentView(text: "DynamicType")
                .previewLayout(.sizeThatFits)
                .environment(\\.sizeCategory, .extraExtraExtraLarge)
                .previewDisplayName("ExtraExtraExtraLargeText")

            ContentView(text: "DarkMode")
                .previewLayout(.sizeThatFits)
                .environment(\\.colorScheme, .dark)
                .previewDisplayName("DarkMode")

            ContentView(text: "LightMode")
                .previewLayout(.sizeThatFits)
                .environment(\\.colorScheme, .light)
                .previewDisplayName("LightMode")
        }
    }
}

Using Preview Providers: If you have a more complex scenario, consider writing custom preview providers to keep your previews organized and easily managed. This is especially useful when you need to preview the views with different configurations or dependencies.

//Example handling the complex scenarios
struct CustomPreviewProvider: PreviewProvider {
    static var previews: some View {
        Group {
            CustomView(configuration: .init(state: .loading))
                .previewDisplayName("LoadingState")

            CustomView(configuration: .init(state: .loaded, data: "SampleData"))
                .previewDisplayName("LoadedState")

            CustomView(configuration: .init(state: .error, errorMessage: "SomethingWentWrong"))
                .previewDisplayName("ErrorState")
        }
    }
}

struct CustomView: View {
    var configuration: Configuration

    var body: some View {
        VStack {
            switch configuration.state {
            case .loading:
                Text("Loading...")
            case .loaded:
                Text(configuration.data ?? "No Data")
            case .error:
                Text(configuration.errorMessage ?? "Error")
                    .foregroundColor(.red)
            }
        }
        .padding()
    }
}

struct Configuration {
    enum State {
        case loading, loaded, error
    }

    var state: State
    var data: String? = nil
    var errorMessage: String? = nil
}

struct CustomView_Previews: PreviewProvider {
    static var previews: some View {
        CustomPreviewProvider.previews
    }
}

Best Practices for Using SwiftUI Previews

1. Modularize Your Code: Create your UI in multiple small, reusable components. This makes your code easier to manage, and means you can preview each component separately.

//Example showing the making reusable component
struct RecipeTitleView: View {
    var title: String

    var body: some View {
        Text(title)
            .font(.headline)
            .padding()
    }
}

struct RecipeTitleView_Previews: PreviewProvider {
    static var previews: some View {
        RecipeTitleView(title: "SampleRecipe")
            .previewLayout(.sizeThatFits)
    }
}

2. Use Group to Organize the Previews: Here we have many previews created, which are grouped under the Group. This way, your previews are more structured and manageable.

When creating multiple previews, use Group to organize them. This makes your previews easier to manage and understand.

// Usage of Group
struct RecipeDetailView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            // here you can manage multiple previews
        }
    }
}

3. Test Different Configurations: Create previews for all different trait configurations, including those with dynamic type sizes, varying color schemes and different devices and displays, to makes your UI reactive and user-friendly.

RecipeDetailView(recipe: sampleRecipes[0])
    .previewDevice("iPhoneSE(2nd generation)")
    .previewDisplayName("iPhoneSE")

4. Leverage Custom Preview Providers: In more complicated scenarios, custom preview providers can help manage various states and configurations.

struct CustomPreviewProvider: PreviewProvider {
    static var previews: some View {
        Group {
            // Custom previews
        }
    }
}

5. Utilize Preview Modifiers: Use preview modifiers like .previewLayout(), .previewDevice() . You can also use .environment() to make your previews more user-friendly and test different conditions.

.previewLayout(.sizeThatFits)
.previewDevice("iPhone14Pro")
.environment(\\.colorScheme, .dark)

Performance Tips

1. Optimize View Hierarchy: Decompose complex view hierarchies to smaller, duplicatable building blocks. Do not use deep-nested views if you want to avoid performance impact.

2. Use Conditional Compilation: Safely add the code you want to run only in debug mode with the #if DEBUG, thus avoiding cluttering your production code.

#if DEBUG
// Debug-only code, will not execute in release build
#endif

3. Limit the Number of Previews: Limit the number of simultaneous preview states to important views and states Group modifier to keep your previews organized.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            // Other previews
        }
    }
}

4. Use Lightweight Data: Use mock data or lightweight versions instead of fetching the original data from network, as that will make making heavy computations.

let sample = ["SampleItem1", "SampleItem2"]

5. Optimize Rendering: Use the .sizeThatFits layout to ensure the view adapts to its content size and avoids over-rendering. Apply the .padding() and use .background() modifier to see it in the context of fixed size.

ContentView()
    .previewLayout(.sizeThatFits)
    .padding()
    .background(Color.gray)

Conclusion

SwiftUI Previews enhance the app development process and enable real-time feedback, fast iteration and visual debugging. This blog post covered what previews are, and how they help developers get realtime feedback to fix UI issues without running the application.

We covered how to add the simplest kind of previews, and modify them with many common modifiers, such as Previews in Multiple States, Dynamic Type and Dark Mode, as well as defining our own Custom Preview Providers. We also included examples of how the feature would behave in real life, along with best practices for good usage, and what to watch out for.

Now that you understand SwiftUI Previews at a high level, we recommend playing around with this concept and exploring more of it on your own. Make experience rendering and try states for views, for example, and play with size classes of your UIs on various devices and with different screen sizes and orientations.

You should also try adjusting your previews with preview modifiers to make your designs more responsive and accessible. Lastly, we discussed the best practices along with performance improvement tips in your application. Happy coding.

Expect The Unexpected!

Debug Faster With Bugfender

Start for Free
blog author

Sachin Siwal

Sachin is an accomplished iOS developer and seasoned technical manager, combining 12 years of mobile app expertise with a knack for leading and delivering innovative solutions. You can contact him on Linkedin

Join thousands of developers
and start fixing bugs faster than ever.