1

Cow you give me some confirmation about my understanding about @ObservedObject and @EnvironmentObject?

In my mind, using an @ObservedObject is useful when we send data "in line" between views that are sequenced, just like in "prepare for" in UIKit while using @EnvironmentObject is more like "singleton" in UIKit. My question is, is my code making the right use of these two teniques? Is this the way are applied in real development?

my model used as brain for funcions (IE urls sessions, other data manipulations)

class ModelClass_ViaObservedObject: ObservableObject {
    
    @Published var isOn: Bool = true

}

 class ModelClass_ViaEnvironment: ObservableObject {
    
    @Published var message: String = "default"
    
}

my main view

struct ContentView: View {
    //way to send data in views step by step
    @StateObject var modelClass_ViaObservedObject = ModelClass_ViaObservedObject()
    
    //way to share data more or less like a singleton
    @StateObject var modelClass_ViaEnvironment = ModelClass_ViaEnvironment() 
    

    var myBackgroundColorView: Color {
        if modelClass_ViaObservedObject.isOn {
            return Color.green
        } else {
            return Color.red
            
        }
    }


    var body: some View {
        
        NavigationView {
            ZStack {
                myBackgroundColorView
                VStack {
                    NavigationLink(destination:
                                    SecondView(modelClass_viaObservedObject: modelClass_ViaObservedObject)
                    ) {
                        Text("Go to secondary view")
                            .padding()
                            .overlay(
                                RoundedRectangle(cornerRadius: 16)
                                    .stroke(.black, lineWidth: 1)
                            )
                    }
                    
                    Text("text received from second view: \(modelClass_ViaEnvironment.message)")
                    
                }
            }
            .navigationTitle("Titolo")
            .navigationBarTitleDisplayMode(.inline)
            
        }
        .environmentObject(modelClass_ViaEnvironment)
        
    }
    
}

my second view

struct SecondView: View {
    
    @Environment(\.dismiss) var dismiss
    
    @ObservedObject var modelClass_viaObservedObject: ModelClass_ViaObservedObject
    
    //global data in environment, not sent step by step view by view
    @EnvironmentObject var modelClass_ViaEnvironment: ModelClass_ViaEnvironment


    
    var body: some View {
        
        VStack(spacing: 5) {
            Text("Second View")
            
            Button("change bool for everyone") {
                modelClass_viaObservedObject.isOn.toggle()
                dismiss()
            }
            
            TextField("send back", text: $modelClass_ViaEnvironment.message)
            Text(modelClass_ViaEnvironment.message)

        }
        
    }
}
biggreentree
  • 1,633
  • 3
  • 20
  • 35
  • 1
    The initial classes which represent `@ObservedObject` and `@EnvironmentObject` are technically the same. Both are a single(ton) source of truth. As you correctly stated the way to hand it over to descendant views is different. – vadian Sep 28 '22 at 08:32
  • Hi Vadian, sharp as Always! So you confirm me this is the right way for handling this mechanism. Thanks! – biggreentree Sep 28 '22 at 09:10

1 Answers1

0

No, we use @State for view data like if a toggle isOn, which can either be a single value itself or a custom struct containing multiple values and mutating funcs. We pass it down the View hierarchy by declaring a let in the child View or use @Binding var if we need write access. Regardless of if we declare it let or @Binding whenever a different value is passed in to the child View's init, SwiftUI will call body automatically (as long as it is actually accessed in body that is).

@StateObject is for when a single value or a custom struct won't do and we need a reference type instead for view data, i.e. if persisting or syncing data (not using the new async/await though because we use .task for that). The object is init before body is called (usually before it is about to appear) and deinit when the View is no longer needed (usually after it disappears).

@EnvironmentObject is usually for the store object that holds model structs in @Published properties and is responsible for saving or syncing,. The difference is the model data is not tied to any particular View, like @State and @StateObject are for view data. This object is usually a singleton, one for the app and one with sample data for when previewing, because it should never be deinit. The advantage of @EnvironmentObject over @ObservedObject is we don't need to pass it down through each View as a let that don't need the object when we only need it further down the hierarchy. Note the reason it has to be passed down as a let and not @ObservedObject is then body would be needlessly called in the intermediate Views because SwiftUI's dependency tracking doesn't work for objects only value types.

Here is some sample code:

struct MyConfig {
    var isOn = false
    var message = ""

    mutating func reset() {
        isOn = false
        message = ""
    }
}

struct MyView: View {
    @State var config = MyConfig() // grouping vars into their struct makes use of value semantics to track changes (a change to any of its properties is detected as a change to the struct itself) and offers testability.

    var body: some View {
        HStack {
            ViewThatOnlyReads(config: config)
            ViewThatWrites(config: $config)
        }
    }
}

struct ViewThatOnlyReads: View {
    let config: MyConfig

    var body: some View {
        Text(config.isOn ? "It's on" : "It's off")
    }
}

struct ViewThatWrites: View {
    @Binding var config: MyConfig
    
    var body: some View {
        Toggle("Is On", isOn: $config.isOn)
    }
}
malhal
  • 26,330
  • 7
  • 115
  • 133
  • thank you for explanation, first of all I'm using the `.toggle() ` just as example, but your point is interesting for me. Could you add some example code? I may find it helpful, since It still seems that the real difference is only if we need to use the same reference in every view or only in some of them and I could use both Environment or `@StateObject ` for logic or url calls. From what you say it seems I should change the use of `@StateObject `, implementing `@Binding ` for such simple features. Could you explain better last 3 lines of your comment? – biggreentree Sep 29 '22 at 08:31
  • Last 3 lines: If you have a View with `let object` then `body` is not called when the object sends its `objectWillChange` (or its `@Published` properties do that for you). If you have `@ObservedObject var object` then body is called. And it's called regardless if `object.someProperty` is even accessed in `body`. This is different from `@State var someState` where `body` is only called if `someState` is accessed in body. Hope that clears it up. – malhal Sep 29 '22 at 09:19
  • Added some sample code – malhal Sep 29 '22 at 09:26