DEV Community

Stoian Dan
Stoian Dan

Posted on

How to populate your #Previews in SwiftUI

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

#Preview {
    BookView()
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)