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