How to preview a custom View that takes bindings as inputs in its initializer?

Say you have the following view:


import SwiftUI

struct BindingViewExample_1 : View {

    @State var value = false

    private var text: String {
        "Toggle is " + (value ? "'on'" : "'off'")
    }

    var body: some View {
        HStack {
            Toggle(isOn: $value) {
                Text(text)
            }
        }
        .padding([.leading, .trailing], 80)
    }
}

#if DEBUG
struct BindingViewExample_1_Previews : PreviewProvider {

    static var previews: some View {
        BindingViewExample_1(value: true)
    }

}
#endif


This previews fine and you can interact with the control in the preview and see the changes immediately.


But what if it took a *binding* as input? Consider this alternate version of the same view:


import SwiftUI

struct BindingViewExample_2 : View {

    @Binding var value: Bool

    private var text: String {
        "Toggle is " + (value ? "'on'" : "'off'")
    }

    var body: some View {
        HStack {
            Toggle(isOn: $value) {
                Text(text)
            }
        }
        .padding([.leading, .trailing], 80)
    }
}

#if DEBUG
struct BindingViewExample_2_Previews : PreviewProvider {

    @State static var value = false

    static var previews: some View {
        BindingViewExample_2(value: $value)
    }

}
#endif


The only differences are in line 5 for the View itself, and in the preview code for both views. This compiles without errors but the preview no longer works. XCode just refuses to even create a preview.


If you keep the View as is but change the preview code to this, instead,


#if DEBUG
struct BindingViewExample_2_Previews : PreviewProvider {

    static var previews: some View {
        BindingViewExample_2(value: .constant(true))
    }

}
#endif


Xcode now builds and displays a preview but interacting with it doesn't update the view. That's not surprising, given the `.constant(true)` binding, but that's the only way I managed to make the preview display when the View takes a binding and that's not very useful (since I can't interact with it).


So... what's the right way to write preview code for a View that takes bindings as inputs?

Post not yet marked as solved Up vote post of oro_boris Down vote post of oro_boris
8.0k views

Answers

I'm not sure how to make the preview interactive but I just added a grouping so I could see the two states as seen below. Hope that helps.

Group { }
#if DEBUG 
struct BindingViewExample_2_Previews : PreviewProvider { 
    static var previews: some View { 
         Group {
             BindingViewExample_2(value: .constant(true)) 
             BindingViewExample_2(value: .constant(false)) 
          }
    } 
} 
#endif

You can add another SwiftUI View that acts as a preview container to hold the state you need for the toggle.


struct BindingViewExamplePreviewContainer_2 : View {
     @State
     private var value = false

     var body: some View {
          BindingViewExample_2(value: $value)
     }
}

#if DEBUG
struct BindingViewExample_2_Previews : PreviewProvider {
    static var previews: some View {
        BindingViewExamplePreviewContainer_2()
    }
}
#endif

I wound up creating a wrapper view specifically to enable proper binding support for previews. The reason using an @State within the preview doesn't work is because the 'previews' property isn't considered a 'body' method by the runtime, and @State complains if you try to get bindings outside of a body method call (it calls fatalError()).


Here's what I use:


struct StatefulPreviewWrapper<Value, Content: View>: View {
    @State var value: Value
    var content: (Binding<Value>) -> Content

    var body: some View {
        content($value)
    }

    init(_ value: Value, content: @escaping (Binding<Value>) -> Content) {
        self._value = State(wrappedValue: value)
        self.content = content
    }
}


This takes a ViewBuilder block to which it passes a Binding ready to use. You then put it into action like so:


struct ContentView: View {
    @Binding var enabled: Bool

    var body: some View {
        Text("Currently enabled? \(enabled ? "Yes" : "No")")
        Toggle("Toggle Me", isOn: $enabled)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        StatefulPreviewWrapper(false) { ContentView(enabled: $0) }
    }
}
  • This is a very good solution. Thanks!

Add a Comment

For what it's worth, this is one of the tools in my toolset on Github.