2

One of the big strengths of SwiftUI is modularity: I don't need to build one large view (controller), I can easily compose my main view from many smaller view components. But developing these view components efficiently requires working live previews.

I noticed that within a Widget target, SwiftUI previews don't work – unless you append the viewContext modifier to the view you want to preview as follows:

struct MyWidgetComponent_Previews: PreviewProvider {
    static var previews: some View {
        MyWidgetComponent()
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

With this modifier, I must choose between one of four fixed widget sizes:

.systemSmall
.systemMedium
.systemLarge
.systemExtraLarge

As a result, my preview will always be squeezed (or expanded) to match the respective widget size. While that makes sense for the main widget view, it's an annoying and useless limitation for widget components.

Example

For example, if I want to create a widget that displays a list of my 5 favorite movies, I would typically create a view for a single movie row and then "for each" that in the widget's main view.

struct MainWidgetView: View {
    let movies: [String]

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            ForEach(Array(zip(movies.indices, movies)), id: \.0) { movie in
                MovieRow(rating: movie.0, title: movie.1)
            }
        }
        .padding()
    }
}

Widget Main View

The MovieRow might be implemented like this:

struct MovieRow: View {
    let rating: Int
    let title: String

    var body: some View {
        HStack(spacing: 16) {
            Text("\(rating)")
                .bold()
                .overlay(
                    Circle()
                        .stroke(.blue, style: .init(lineWidth: 2))
                        .padding(-8)
                )

            Text(title)
        }
    }
}

When I now want to preview a single MovieRow, I'll have to inject the previewContext:

struct MovieRow_Previews: PreviewProvider {
    static var previews: some View {
        MovieRow(rating: 5, title: "Star Trek IV: The Voyage Home")
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

where I actually want a custom size or (even better:) self-sizing.

Widget Component View

It might not be a big issue in this example where the text fits nicely into the widget size with only some padding around it, but I can think of many situations where squeezing or expanding a view component is completely impractical an distorts the outcome.

Question

Is there a way to preview isolated views that are used to compose a widget without providing a view context and use a custom size (or self-sizing) instead?

If so, how?

1 Answer 1

1

Yes, you can use .previewLayout for that. It provides .fixed(width:height:) and .sizeThatFits

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(.fixed(width: 300, height: 300))
        
        ContentView()
            .previewLayout(.sizeThatFits)
    }
}

enter image description here

Sign up to request clarification or add additional context in comments.

4 Comments

Have you tested that within a widget target (i.e. where the file that contains the preview is not part of the app's main target)? Because for me, this doesn't work unfortunately and throws the same error.
I thought so but I realized I put the test file on the wrong target 🙃 but I guess it would work if you add the View to your app target as well, at least for preview purposes. But that does not seem ideal. Sorry for the confusion
No worries. Thanks for trying to help! Yes, a solution would be to create a separate target and put all widget subviews incl. their previews in there. But why would Apple force us to do that? 🤔
Hey did you ever find a solution to this? I have the exact same problem, and I would like to avoid putting widget-only views in my main app target just to be able to preview them...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.