I’m looking for a clean solution to resolve this SwiftUI challenge.
The following code compiles but do not work since @State
property is outside the ContentView
scope.
import SwiftUI
struct ContentView: View {
var state: LocalState?
var body: some View {
if let state = state {
Toggle("Toggle", isOn: state.$isOn)
}
}
}
extension ContentView {
struct LocalState {
@State var isOn: Bool
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
VStack {
ContentView(
state: .init(isOn: false)
)
.border(Color.red)
ContentView()
.border(Color.red)
}
}
}
The following code doesn’t compile since the following reasons:
Value of optional type ‘ContentView.LocalState?’ must be unwrapped to refer to member ‘isOn’ of wrapped base type ‘ContentView.LocalState’
It seems that $
in $state.isOn
refer to the original state
and not to the unwrapped one.
import SwiftUI
struct ContentView: View {
@State var state: LocalState!
var body: some View {
if let state = state {
Toggle("Toggle", isOn: $state.isOn)
}
}
}
extension ContentView {
struct LocalState {
var isOn: Bool
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
VStack {
ContentView(
state: .init(isOn: false)
)
.border(Color.red)
ContentView()
.border(Color.red)
}
}
}
What I do NOT want is:
- use of failable initializer in ContentView.
- move
isOn
property outsideLocalState
.
How can I achieve those?
3
Answers
This works for me:
Breaking it down:
$state
is aBinding<LocalState?>
, and we use theBinding
initialiser (hopefully that’s not the failable initialiser that you don’t want to use) to convert it to aBinding<LocalState>?
. Then we can use optional chaining andif let
to get aBinding<Bool>
out of it.Related: How can I unwrap an optional value inside a binding in Swift?
$state
is syntactic sugar for_state.projectedValue
, which gives you aBinding<LocalState?>
. And from here on things are ugly.You might be able to get away with a wrapped binding:
And then:
And alternative, inspired by @Sweeper’s answer:
I believe this can be solved with two techniques. 1. using the Binding constructor that can create a non-optional binding from an optional. And 2. use of a constant binding in previews, e.g.