0

We needed to bind to the @Published in a ParentView's @StateObject being used as a ViewModel. I told my coworker we needed to pass in the entire ViewModel to an @ObservedObject in the ChildView, since everything I've read about SwiftUI describes the parent-child data relationships like this:

Parent View Child View
@State @Binding
@StateObject @ObservedObject
@Published (within the @StateObject) DOESN'T EXIST

He insisted we could just pass in the @Published alone to the ChildView's @Binding and it would work. Lo and behold, he was right. In the example code below is the documented way to use @Binding, along with passing the entire @StateObject down into an @ObservedObject and binding to the @Published directly (which I thought was the only way you could/should bind to an @Published), and passing the @Published directly into the @Binding, which surprisingly does work. You can see in the sample pic that when you type in any of the 3 TextField's, the updates appear immediately in the parent view, proving that the @Binding works with all 3 approaches.

Is this a fluke about binding to the @Published directly? Or an undocumented-but-valid approach? Why isn't this taught as a standard approach?

import SwiftUI

class MyViewModel: ObservableObject {
    @Published var publishedStr1 = "I am publishedStr1"
    @Published var publishedStr2 = "I am publishedStr2"
}

struct ParentView: View {
    @State var stateStr = "I am stateStr"
    @StateObject var myViewModel = MyViewModel()
    
    var body: some View {
        VStack {
            ChildView(stateStr: $stateStr, 
                      myViewModel: myViewModel, 
                      // Why don't I ever see examples using this technique
                      // of passing the @Published directly to an @Binding?
                      // It clearly works.
                      publishedStr2: $myViewModel.publishedStr2)
            Text(stateStr)
            Text(myViewModel.publishedStr1)
            Text(myViewModel.publishedStr2)
        }
    }
}

struct ChildView: View {
    @Binding var stateStr: String
    @ObservedObject var myViewModel: MyViewModel
    @Binding var publishedStr2: String

    var body: some View {
        VStack {
            TextField("Binding to State", text: $stateStr)
                .textFieldStyle(RoundedBorderTextFieldStyle())

            TextField("Binding to ObservedObject",
                      text: $myViewModel.publishedStr1)
            .textFieldStyle(RoundedBorderTextFieldStyle())

            TextField("Binding to ObservedObject's Published",
                      text: $publishedStr2)
            .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
}

TextFields updating @Binding in Simulator

James Toomey
  • 5,635
  • 3
  • 37
  • 41
  • 1
    Nah, `@Published` → `@Binding` is pretty common — see https://stackoverflow.com/a/59260001/14351818. Although I usually prefer passing down the entire model — I've found it's a bit less glitchy especially when you have a bunch of views – aheze Nov 22 '22 at 08:09
  • 1
    Thanks for the response on this. Curious why it hasn't been presented more in the various tutorials I saw when learning SwiftU (or even in Apple docs). It makes sense to pass down the enter viewmodel like you said, though, especially since later on there's a good chance you'll need to modify some other property in the viewmodel, which is easier if you already have it there. – James Toomey Nov 22 '22 at 14:42

1 Answers1

0

@StateObject is only when we need a reference type for state. In your case you don't so just use @State, e.g.

struct ParentViewConfig {
    var publishedStr1 = "I am publishedStr1"
    var publishedStr2 = "I am publishedStr2"
    var stateStr = "I am stateStr"

    mutating func someFunc() {

    }
}

struct ParentView: View {
    @State var config = ParentViewConfig()
malhal
  • 26,330
  • 7
  • 115
  • 133