mallow
mallow

Reputation: 2866

Mutable Binding in SwiftUI Live Preview

I have a ChildView with a variable:

@Binding var itemName: String

In this ChildView I have few buttons that change value of the variable:

Button(action: {
    self.itemName = "different value"
})

I was trying to use Preview like this:

struct ChildView_Previews: PreviewProvider {
    static var previews: some View {
        ChildView(itemName: "test")
    }
}

But I am getting an error:

Cannot convert value of type 'String' to expected argument type 'Binding'

I am aware that I can use Preview like below. And the error will be gone and preview will work, but... itemName will have constant value, it will not be mutable now, not interactive in Live Preview:

struct ChildView_Previews: PreviewProvider {
    static var previews: some View {
        ChildView(itemName: .constant("test"))
    }
}

How to declare a binding in SwiftUI Preview to make it interactive?

Upvotes: 17

Views: 3165

Answers (2)

Fabian Streitel
Fabian Streitel

Reputation: 2800

If you need a value that can be changed in the live preview, I like to use this helper class:

struct BindingProvider<StateT, Content: View>: View {
    
    @State private var state: StateT
    private var content: (_ binding: Binding<StateT>) -> Content
    
    init(_ initialState: StateT, @ViewBuilder content: @escaping (_ binding: Binding<StateT>) -> Content) {
        self.content = content
        self._state = State(initialValue: initialState)
    }
    
    var body: some View {
        self.content($state)
    }
}

Use it like so:

struct YourView_Previews: PreviewProvider {
    
    static var previews: some View {
        var yourVar = "example"
        BindingProvider(yourVar) { binding in
            YourView(initVar: binding)
        }
    }

}

This allows you to test changing the binding in the live preview.

Upvotes: 9

Asperi
Asperi

Reputation: 258591

Updates to a @State variable in a PreviewProvider appear to not update the the read-only computed property previews directly. The solution is to wrap the @State variable in a test holder view. Then use this test view inside the previews property so the Live Preview refreshes correctly. Tested and working in Xcode 11.2.1.

struct ChildView: View {
    @Binding var itemName: String

    var body: some View {
        VStack {
            Text("Name: \(itemName)")
            Button(action: {
                self.itemName = "different value"
            }) {
                Text("Change")
            }
        }
    }
}

struct ChildView_Previews: PreviewProvider {

    struct BindingTestHolder: View {
        @State var testItem: String = "Initial"
        var body: some View {
            ChildView(itemName: $testItem)
        }
    }

    static var previews: some View {
        BindingTestHolder()
    }
}

Upvotes: 20

Related Questions