Essentially, I'm nesting @Binding
3 layers deep.
struct LayerOne: View {
@State private var doubleValue = 0.0
var body: some View {
LayerTwo(doubleValue: $doubleValue)
}
}
struct LayerTwo: View {
@Binding var doubleValue: Double {
didSet {
print(doubleValue)
}
}
var body: some View {
LayerThree(doubleValue: $doubleValue)
}
}
struct LayerThree: View {
@Binding var doubleValue: Double {
didSet {
print(doubleValue) // Only this print gets run when doubleValue is updated from this struct
}
}
var body: Some view {
// Button here changes doubleValue
}
}
Whichever struct I change doubleValue
in is the one where the didSet
will get run, so for example if I change it in LayerThree
only that one will print, none of the others will.
I am able to watch for changes with .onChange(of: doubleValue)
which will then get run when it changes but it's not making sense to me why didSet
won't get run except on the struct where it's changed from.
Is @Binding
struct specific?
CodePudding user response:
Using property observers like didSet
on values wrapped in PropertyWrappers
will not have the "normal" effect because the value is being set inside the wrapper.
In SwiftUI, if you want to trigger an action when a value changes, you should use the onChange(of:perform:)
modifier.
struct LayerTwo: View {
@Binding var doubleValue: Double
var body: some View {
LayerThree(doubleValue: $doubleValue)
.onChange(of: doubleValue) { newValue
print(newValue)
}
}
}
CodePudding user response:
Now all working:
struct ContentView: View {
var body: some View {
LayerOne()
}
}
struct LayerOne: View {
@State private var doubleValue:Double = 0.0 {
didSet {
print("LayerOne:", doubleValue)
}
}
var body: some View {
LayerTwo(doubleValue: Binding(get: { return doubleValue }, set: { newValue in doubleValue = newValue } ))
}
}
struct LayerTwo: View {
@Binding var doubleValue: Double {
didSet {
print("LayerTwo:", doubleValue)
}
}
var body: some View {
LayerThree(doubleValue: Binding(get: { return doubleValue }, set: { newValue in doubleValue = newValue } ))
}
}
struct LayerThree: View {
@Binding var doubleValue: Double {
didSet {
print("LayerThree:", doubleValue)
}
}
var body: some View {
Text(String(describing: doubleValue))
Button("update value") {
doubleValue = Double.random(in: 0.0...100.0)
}
.padding()
}
}
print results:
LayerOne: 64.58963263686678
LayerTwo: 64.58963263686678
LayerThree: 64.58963263686678
CodePudding user response:
To see why this happens, we can unveil the syntactic sugar of property wrappers. @Binding var doubleValue: Double
translates to:
private var _doubleValue: Binding<Double>
var doubleValue: Double {
get { _doubleValue.wrappedValue }
set { _doubleValue.wrappedValue = newValue }
}
init(doubleValue: Binding<Double>) {
_doubleValue = doubleValue
}
Whatever you do in didSet
will be put after the line _doubleValue.wrappedValue = newValue
. It should be very obvious why when you update doubleValue
in layer 3, The didSet
of doubleValue
in layer 2 or 1 doesn't get called. They are simply different computed properties!
swiftPunk's solution works by creating a new binding whose setter sets the struct's doubleValue
, hence calling didSet
:
Binding(get: { return doubleValue },
set: { newValue in doubleValue = newValue }
// ^^^^^^^^^^^^^^^^^^^^^^
// this will call didSet in the current layer