In SwiftUI, @State is defined as:
@frozen @propertyWrapper public struct State<Value> : DynamicProperty {
And it has a computed property:
public var wrappedValue: Value { get nonmutating set }
Question:
- why doesn't
wrappedValueneed to implement the getter and setter? what is this shorthand syntax? - what's the intention of
nonmutating set? is it trying to disable the setter? - questions around the
nonmutating setis sometimes mutating in the following snippet:
struct PlayButtonStackOverflow: View {
@State private var isPlaying: Bool = false
var body: some View {
// TODO: 3.3: this does NOT change the state. why?
$isPlaying.wrappedValue = true
print($isPlaying.wrappedValue.description) // prints false
return Button(isPlaying ? "isPlaying = true" : "isPlaying = false") {
// TODO: 3.1: this does change the state.
$isPlaying.wrappedValue = true
// TODO: 3.1: and this prints true, why?
print($isPlaying.wrappedValue )
// `$isPlaying.wrappedValue` is accessing the State.projectValue, which is a Binding, and the Binding.wrappedValue has a non mutating setter. how come setter is actually mutating?
// TODO: 3.2: Compile error: Referencing property 'wrappedValue' requires wrapper 'Binding<Bool>'
// why? is there a way to access the State.wrappedValue setter? How is it being disabled?
// isPlaying.wrappedValue = true
}.padding()
}
}
Update A:
Line at TODO 3.3 gives Modifying state during view update, this will cause undefined behavior. That explains some part of it. Since a state change triggers the body to evaluate again. But in this case, all mutation is in one direction.
Question A.1: why is TODO 3.3 still prints false?
Question A.2: trying to wrap my head around this, is $isPlaying.wrappedValue = true and isPlaying = true having the same effect at line TODO 3.3?
Question A.3: since Binding is a struct, passing a value type around create copies of it. But in this case passing around $isPlaying to another func can mutate some value at call site? why?
isPlayingstate getter somewhere inbodyfor the set to be detected andbodyto be called again. SwiftUI tracks these dependencies.let _ = isPlayingat the top ofbodywould do it.It might help to mention the
$syntax is sugar forBinding(get: { }, set: { })i.e. a pair of 2 closures. Declaring$isPlaying, i.e.Binding(get: { self.isPlaying }, set { self.isPlaying - newValue})does not call the@State var isPlayinggetter immediately so is not tracked as a dependency onbody.Property wrappers also have an
mutating func update(), which SwiftUI calls before callingbody, docs say "SwiftUI calls this function before rendering a view’s body to ensure the view has the most recent value.". It's possible that this gets the value back out of the internal object and sets it on this struct.