TechnologiesSwiftUI

PreviewModifier protocol

iOSmacOStvOSwatchOSvisionOSiOS 18.0+✓ renders

A type that defines an environment in which previews can appear.

How it works

PreviewModifier is a protocol you adopt to wrap a preview in shared setup before SwiftUI renders it, and to build any expensive or asynchronous context that setup depends on exactly once. It solves a common preview problem: many previews need the same scaffolding — sample data, an injected environment, a configured model store — and recomputing that per preview is wasteful and repetitive. Reach for PreviewModifier when several previews should share identical context, or when establishing that context requires async work that you'd rather perform a single time. SwiftUI caches the shared context by type and reuses it across every preview that applies the same modifier.

  1. Conform a type to PreviewModifier

    Declare a value type that adopts the protocol so it can participate in preview rendering. Here SampleData is a small struct conforming to PreviewModifier, giving you one place to define both the data and how it's applied to each preview that uses it.

  2. Produce the shared context in makeSharedContext()

    Implement the asynchronous makeSharedContext() requirement to build the value the previews share; SwiftUI runs it once and reuses the result. Its return type defines the modifier's Context associated type — makeSharedContext() returns [String] (["Ada", "Linus", "Grace"]), and because it's async throws you can await real setup work or surface failures.

  3. Apply the context in body(content:context:)

    Implement body(content:context:) to transform the preview's content using the shared value, returning some View. The content parameter is the preview SwiftUI hands you, and the context parameter is whatever makeSharedContext() produced; here body injects it with content.environment(\.previewNames, context) so downstream views can read it.

  4. Read the injected context downstream

    Because the modifier feeds context through the environment, the previewed view consumes it with the matching property wrapper. The demo exposes a previewNames EnvironmentValues key (backed by PreviewNamesKey) and reads it via @Environment(\.previewNames) private var names, the same channel body wrote to.

Try it — Add a fourth name to the array returned by makeSharedContext() (e.g. change ["Ada", "Linus", "Grace"] to ["Ada", "Linus", "Grace", "Alan"]) and observe the shared context flow into the preview's environment.

Example & preview

Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.

PreviewModifier.swift
struct PreviewModifierDemo: View {
    struct SampleData: PreviewModifier {
        func makeSharedContext() async throws -> [String] {
            ["Ada", "Linus", "Grace"]
        }
        func body(content: Content, context: [String]) -> some View {
            content.environment(\.previewNames, context)
        }
    }

    @Environment(\.previewNames) private var names

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("Shared Preview Context")
                .font(.headline)
            ForEach(["Ada", "Linus", "Grace"], id: \.self) { name in
                Label(name, systemImage: "person.circle")
            }
        }
        .padding()
    }
}

private struct PreviewNamesKey: EnvironmentKey {
    static let defaultValue: [String] = []
}

extension EnvironmentValues {
    var previewNames: [String] {
        get { self[PreviewNamesKey.self] }
        set { self[PreviewNamesKey.self] = newValue }
    }
}
Live preview
Shared Preview Context
Shared Preview Context
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →