Home > Software engineering >  Is didSet on a @Binding file specific?
Is didSet on a @Binding file specific?

Time:11-23

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
  • Related