Testing SwiftUI Bindings in Xcode Previews
This post is brought to you by Emerge Tools, the best way to build on mobile.
Xcode Previews exist to make our collective development efforts much easier. To break us from the decade long shackles of code, build, run and test. Recently, however, it occurred to me that I wasn’t sure off the top of my head of how to handle @Binding
variables I want to test in Xcode Previews. Consider this:
struct Person: Identifiable {
let id = UUID()
var name: String
var age: Int
}
struct PersonEditor: View {
@State var person: Person = Person(name: "Jordan", age: 33)
var body: some View {
Form {
Section("Details") {
TextField("Name", text: $person.name)
Stepper("Age: \(person.age)", value: $person.age, in:1...100, step: 1) { changed in
// No op
}
}
}
}
}
Simple enough - this works perfectly fine in the Xcode simulator because person
is a @State variable
- as such, we are free to mutate it as much as we want:
However, later on after you’ve prototyped your view to perfection, that @State
variable is likely to turn into a @Binding
. And so you make a simple change:
@Binding var person: Person
And in your PreviewProvider
for the view, you make the switch and probably use .constant()
for the binding:
struct PersonEditor_Previews: PreviewProvider {
static var previews: some View {
PersonEditor(person: .constant(Person(name: "Jordan", age: 33)))
}
}
Only now, we’ve got a problem. We can’t mutate the data at all in Xcode Previews…which kinda defeats the entire purpose of the whole thing. I don’t want to build and run my app to try this out, I just want to do it here.
In my compendium of Xcode Preview snips, I didn’t have an obvious answer to this. And if you hit the Googles, you’ll find that most solutions recommend the use of .constant
- which of course has its place, but it’s useless if you want to actually test mutating your models or what have you.
If you’re new to SwiftUI, maybe you’d try this next:
struct PersonEditor_Previews: PreviewProvider {
@State private var person = Person(name: "Jordan", age: 33)
static var previews: some View {
PersonEditor(person: $person)
}
}
But alas, that won’t even compile. You’ll be met with Instance member '$person' cannot be used on type 'PersonEditor_Previews'
, which absolutely makes sense as previews
are statically represented. So what’s an iOS developer to do?
Turns out, watch the dub dub video. In the WWDC 2020 video, “Structure your app for SwiftUI previews”, at around the 16 minute mark Apple gives us the answer: use a container view in your PreviewProvider
:
struct PersonEditor_Previews: PreviewProvider {
// A View that simply wraps the real view we're working on
// Its only purpose is to hold state
struct PersonEditorContainer: View {
@State private var person = Person(name: "Jordan", age: 33)
var body: some View {
PersonEditor(person: $person)
}
}
// Now, use that view wrapper here and we can mutate bindings
static var previews: some View {
PersonEditorContainer()
}
}
And just like that, you’re good to go. By adding a little View
that holds the state for you, you’re free to use the interface you’re working on as you actually would when it’s live.
Until next time ✌️