Ian Warburton
Ian Warburton

Reputation: 15726

How do I pass a subset of data to a view that has its own view model?

Given the following...

import SwiftUI

class ViewModel: ObservableObject {
    
    var value: Bool
    
    init(value: Bool) {
        self.value = value
    }
    
    func update() {
        value = !value
    }
}

struct A: View {
    
    @ObservedObject let viewModel: ViewModel
    
    init(value: Bool) {
        viewModel = ViewModel(value: value)
    }
    
    var body: some View {
        Text("\(String(viewModel.value))")
            .onTapGesture {
                viewModel.update()
            }
    }
}

struct B: View {
    @State var val = [true, false, true]

    var body: some View {
        A(value: val[0])
    }
}

How do I get viewModel to update B's val? It looks like I should be able to use @Binding inside of A but I can't use @Binding inside ViewModel, which is where I want the modification code to run. Then, I don't think I'd need @ObservedObject because the renders would flow from B.

Upvotes: 0

Views: 192

Answers (2)

New Dev
New Dev

Reputation: 49590

If you want to update the value owned by a parent, you need to pass a Binding from the parent to the child. The child changes the Binding, which updates the value for the parent.

Then you'd need to update that Binding when the child's own view model updates. You can do this by subscribing to a @Published property:

struct A: View {
    @ObservedObject var viewModel: ViewModel
    
    @Binding var value: Bool // add a binding
    
    init(value: Binding<Bool>) {
        _value = value
        viewModel = ViewModel(value: _value.wrappedValue)
    }
    
    var body: some View {
        Button("\(String(viewModel.value))") {
            viewModel.update()
        }
        // subscribe to changes in view model
        .onReceive(viewModel.$value, perform: {
            value = $0 // update the binding
        })
    }
}

Also, don't forget to actually make the view model's property @Published:

class ViewModel: ObservableObject {    
   @Published var value: Bool
   // ...
}

Upvotes: 1

user652038
user652038

Reputation:

You either need Binding, or an equivalent that does the same thing, in ViewModel. Why do you say you can't use it?

struct A: View {
  @ObservedObject var model: Model

  init(value: Binding<Bool>) {
    model = .init(value: value)
  }

  var body: some View {
    Text(String(model.value))
      .onTapGesture(perform: model.update)
  }
}

extension A {
  final class Model: ObservableObject {
    @Binding private(set) var value: Bool

    init(value: Binding<Bool>) {
      _value = value
    }

    func update() {
      value.toggle()
    }
  }
}
struct B: View {
  @State var val = [true, false, true]

  var body: some View {
    A(value: $val[0])
  }
}

Upvotes: 1

Related Questions