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.
Top comments (0)