How to populate your #Previews in SwiftUI



This content originally appeared on DEV Community and was authored by Stoian Dan

Apple created the #Preview macro to more easily preview your SwiftUI views in Xcode:

#Preview {
    BookView()
}

You can even write some handy code in a #Preview:

#Preview {
    let books = ["Eros&Agape, by Anders Nygren", "Commentary to Galatians, by Martin Luther"]

    BookView(books: books)
}

However, what do you do when you want to cache the data needed for preview initialization or, another use case, if you need to use async code to get that data in the first place?

While initially I saw approaches like:

#Preview {
    @Previewable @State var books: [Book]  = []

    if books.isEmpty {
        ProgressView()
          .task {
             books = await myAsyncFunc()
          }
    } else {
        BookView(books: books)
    }
}

This was a hacky way to do it and it also didn’t provide caching. So here’s the more Apple way — that I wish Apple would advertise more , as they’re documentation doesn’t really provide examples in general 😩 — to do it:

struct BookProvider: PreviewModifier {
    static func makeSharedContext() async -> [Book] {
             return await myAsyncBookGenerator()
    }


    func body(content: Content, context: [Book]) -> some View {
        BookView(books: context)
    }
}

In essence, you can use makeSharedContext to generate whatever type you want – by default it’s Void, so you can also entirely omit this function – then, via the body method, you get your content (whatever the #Preview provides), your context (whatever makeSharedContext provides); finally you have your chance to return whatever the newly modified view! That’s it!

To apply the modifier, just:

#Preview(traits: .modifier(BookProvider())) {
    BookView()
}

I’m just wondering whether or not Apple could have used generics (probably not? because you can’t really do that in protocols…) to not have that Content type be a type erased view.

P.S.
I’ll admit my use-case is a bit strange because I don’t really make use of the content variable – I can’t, because it’s typed erased – I just directly return a view no matter what it’s being thrown by the client #Preview.


This content originally appeared on DEV Community and was authored by Stoian Dan