Mischa
Mischa

Reputation: 17241

Preview isolated SwiftUI views in a widget with a custom size

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?

Upvotes: 2

Views: 2295

Answers (1)

hallo
hallo

Reputation: 1027

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

Upvotes: 1

Related Questions