Nicolas Lefebvre
Nicolas Lefebvre

Reputation: 4282

Why does adding an intermediate view or referring to a sub property break my data binding?

I appear to be misunderstanding something fundamental about Swift / SwiftUI, because I am struggling to understand what is happening in the minimal example below when I introduce an additional view or an additional level indirection.

Here's a simple model consisting of a hierarchy of ObservableObject, and some simple views to manipulate it.

import SwiftUI

class Model : ObservableObject {
    @Published var sub = SubModel()
}

class SubModel: ObservableObject {
    @Published var hue: Double = 0.0
}

struct PickerView: View {
    @Binding var hue: Double
       
    var body: some View {
        Slider(value: $hue, in: 0...255)
    }
}

struct TextView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        Text(verbatim: String(model.sub.hue))
    }
}

struct PickerWrapperView: View {
    @ObservedObject var sub: SubModel
    
    var body: some View {
        PickerView(hue: $sub.hue)
    }
}

struct ContentView: View {
    @ObservedObject var model: Model
            
    var body: some View {
        VStack {
            //PickerWrapperView(sub: model.sub)
            PickerView(hue: $model.sub.hue)
            
            TextView(model: model)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(model: Model())
    }
}

Now, as is, this works as expected: when moving the slider, the text updates to the new value.

However:

What probably simple concept am I missing here?

Upvotes: 0

Views: 68

Answers (1)

try this approach, using struct SubModel instead of nesting ObservableObject. With this approach, any change to the SubModel will be observed by the Model directly, even with intermediate view.

class Model : ObservableObject {
    @Published var sub = SubModel()
}

struct SubModel: Hashable {  // <-- here
    var hue: Double = 0.0
}

struct TextView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        Text("\(model.sub.hue)") // <-- here
    }
}

struct PickerView: View {
    @Binding var hue: Double
       
    var body: some View {
        Slider(value: $hue, in: 0...255)
    }
}

struct PickerWrapperView: View {
    @Binding var sub: SubModel
//    @ObservedObject var model: Model
    
    var body: some View {
        PickerView(hue: $sub.hue)
//        PickerView(hue: $model.sub.hue)
    }
}

struct ContentView: View {
    @StateObject var model = Model() // <-- for testing
            
    var body: some View {
        VStack {
            
               PickerWrapperView(sub: $model.sub) // <-- here with binding
            //   PickerWrapperView(model: model) // <-- here with ObservedObject
            
            // model.sub.hue, will be updated by the SLider
     //       PickerView(hue: $model.sub.hue)
            // the Text in TextView will be updated whenever the model changes
            TextView(model: model)
        }
    }
}

Upvotes: 1

Related Questions