How to create a SwiftUI #Preview in Xcode 15 for a view with a @Binding
Asked Answered
N

7

30

If I wanted to create a preview for a SwiftUI view that contains a @Binding I would have previously written something like this:

struct SpecialButton_Preview: PreviewProvider {
    static var previews: some View {
        @State var value: Bool = true
        SpecialButton(isOn: $value)
    }
}

However Xcode 15 now comes with a new syntax (#Preview) but when I try to add my example state property, it does not work:

#Preview {  // Error: Ambiguous use of 'Preview(_:traits:body:)'
    @State var value: Bool = true
    SpecialButton(isOn: $value)
}

How can I make this work?

Negus answered 13/6, 2023 at 19:26 Comment(1)
You can still use the old syntax if you want toBedivere
H
45

You need to return the View to preview. I'm not exactly sure how this works, it has to do with how Swift macros work.

#Preview {
    @State var value: Bool = true
    return SpecialButton(isOn: $value)
}

From the WWDC Slack: "The new #Previews macro simply takes a closure that returns the thing to be previewed. So you can declare local variables or do other setup necessary in there just like you would in any other closure."

Edit: This only works when you don't want to update the state, otherwise you have to follow the way mentioned by de.

Heterosexuality answered 13/6, 2023 at 20:20 Comment(3)
This gives me the following error: Cannot use explicit 'return' statement in the body of result builder 'ViewBuilder'Karaganda
@Karaganda what did your whole code snippet look like? I put this in Xcode 15 beta 1 and it worked (also note this is subject to change because this is a beta Xcode right now)Heterosexuality
Doing this and updating @State variables won't actually trigger a view update. It is needed to wrap it into a wrapper view as suggested by de. below.Alcestis
N
38

This is what I ended up doing in order to have a mutable value:


#Preview {
    struct PreviewWrapper: View {
        @State var value: Bool = true
        
        var body: some View {
            SpecialButton(isOn: $value)
        }
    }
    return PreviewWrapper()
}

Negus answered 14/6, 2023 at 21:7 Comment(4)
What does doing this accomplish? in this case, you could use what is mentioned here: https://mcmap.net/q/466699/-how-to-create-a-swiftui-preview-in-xcode-15-for-a-view-with-a-bindingHeterosexuality
Yep, I am also using preview wrapper to accomplish real stateful previews. Otherwise, state values will not trigger preview updates.Calibrate
Correct way!, This was recommended by apple in WWDC 2020 to make sure @state change triggers view change.Lyndseylyndsie
Thanks for the reference! I was hoping they would improve this with the new #Preview macro though.Negus
D
7

What you could do before and still can do is:

SpecialButton(isOn: .constant(true))
Dolora answered 13/6, 2023 at 20:13 Comment(0)
S
1

Thanks - I have a similar error: Ambiguous use of 'init(_:traits:body:)'

#Preview {
    NewPositionView(symbol: .constant("AAPL") )
}

& with a @State var without the return inside of Preview Error: Result of 'NewPositionView' initializer is unused

This fixed it... adding the return inside #Preview

#Preview {
    @State var sym: String = "AAPL"
    return NewPositionView(symbol: $sym )
}
Sine answered 30/12, 2023 at 3:16 Comment(1)
But can you mutate the state interactively like in the version with the WrapperView?Negus
P
1

I personally have a reusable view that I use to allow for the @Binding to be manipulated in the Live Preview. It looks like the following:

public struct PreviewBindingWrapper<T, Content: View>: View {
    @State private var wrappedBinding: T
    private let content: (Binding<T>) -> Content

    public init(wrappedBinding: T, @ViewBuilder content: @escaping (Binding<T>) -> Content) {
        self._wrappedBinding = State(wrappedValue: wrappedBinding)
        self.content = content
    }

    public var body: some View {
        content($wrappedBinding)
    }
}

This can then be used in your preview like:

#Preview {
    PreviewBindingWrapper(true) { booleanBinding in
        SpecialButton(isOn: booleanBinding)
    }
}

Hope this helps!

Posen answered 12/2 at 21:33 Comment(0)
C
1

iOS 18.0+, macOS 15.0+, watchOS 11.0+, visionOS 2.0+

You can now achieve this using the new @Previewable:

#Preview {
    @Previewable @State var value = true
    return SpecialButton(isOn: $value)
}
Conductivity answered 11/6 at 7:48 Comment(4)
any idea how to make this compile in xcode15?Entero
@Entero You will need to use Xcode 16 for this. Currently, it's in beta and can be installed from developer.apple.com/download/applicationsConductivity
@AlexanderSandberg According to the documentation linked above, Previewable should be available in iOS 17+, but it makes no mention of required Xcode version. If it supports iOS 17, shouldn't it be available in Xcode 15?Uxmal
Seems it is indeed an Xcode 16 (beta for now) feature.Uxmal
K
-3

Do you need to keep it as a binding var in your preview? Otherwise try this, as it seems to work for me:

#Preview {
    SpecialButton(isOn: true)
}

I'd elaborate on the 'why' but that's still unclear!

Karaganda answered 15/6, 2023 at 7:57 Comment(2)
Yes, the whole point is to preview "a SwiftUI view that contains a @Binding".Negus
@Negus ...which my solution does, in a way? In my seemingly identical case, it allows my View containing a @Binding to be previewed whereas none of the other currently stated solutions work for me. Are you not getting Cannot use explicit 'return' statement in the body of result builder 'ViewBuilder' when you try the accepted solution, as I did when I tried it with the code you've provided?Karaganda

© 2022 - 2024 — McMap. All rights reserved.