swiftuiobservedobjectenvironmentobject

Is this the right way for using @ObservedObject and @EnvironmentObject?


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)

        }
        
    }
}

Solution

  • 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)
        }
    }