Apple added the Previewable macro to iOS 18. This generates the boilerplate wrapper view you need to preview a view with State bindings.
Whatโs The Problem?
I have mixed feelings about SwiftUI previews. When they work they can make creating a view an interactive experience. They can also be slow, break with confusing errors, and need boilerplate container views to create any state bindings.
For that last point consider this form view that expects a binding to a bool:
struct SettingsView: View {
@Binding var showMessages: Bool
var body: some View {
NavigationStack {
Form {
Section("General Settings") {
Toggle("Show messages", isOn: $showMessages)
}
...
}
.navigationTitle("Settings")
}
}
}
When previewing this view we need a binding. One way is to provide a constant value:
#Preview {
SettingsView(showMessages: .constant(true))
}
That works but prevents us from interacting with the view. A better approach is to create a wrapper view that supplies the mutable state:
private struct ContainerView: View {
@State var showMessages: Bool = false
var body: some View {
SettingsView(showMessages: $showMessages)
}
}
#Preview {
ContainerView()
}
That gives us a preview with a working toggle button, but creating a wrapper view for our state each time gets to be tiresome.
The Previewable Macro
In Xcode 16, you can replace that boilerplate wrapper code with the Swift Previewable macro:
#Preview {
@Previewable @State var showMessages: Bool = false
SettingsView(showMessages: $showMessages)
}
Declare any @State properties you need at the root level of the Preview and mark them with the @Previewable macro. A SwiftUI wrapper view is automatically generated containing the state properties. Expanding the Preview macro (abbreviated):
static func makePreview() throws -> DeveloperToolsSupport.Preview {
DeveloperToolsSupport.Preview {
struct __P_Previewable_Transform_Wrapper: SwiftUI.View {
@State var showMessages: Bool = false
var body: some SwiftUI.View {
SettingsView(showMessages: $showMessages)
}
}
return __P_Previewable_Transform_Wrapper()
}
}
That looks pretty close to our original wrapper view.
Note: The @Previewable macro deploys back to iOS 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, and watchOS 10.0.