Hello, I am trying to build an app using SwiftUI that uses 3 classes for the model. One is a source of truth for the other two. I'd like to have changes made to the model through interactions with the UI remain consistent through all three classes. I think I have a solution but I want to understand if I am on the right track in terms of how to EnvironmentObject and ObservedObject. Here is the "minimum" amount of code needed to see how things are working.
I add the following lines to SceneDelegate.swift:
let gd = GameData(maxValue: 10, player: "Player 1")
let ps = ProblemSpace(gameData: gd)
let sm = StatsManager(gameData: gd)
let contentView = CombineTestView(problemSpace: ps, statsManager: sm)
.environmentObject(gd)
and then add the following to a new SwiftUI view:
import Combine
class GameData: ObservableObject {
@Published var maxValue: Int
@Published var player: String
init(maxValue: Int, player: String) {
self.maxValue = maxValue
self.player = player
}
}
class ProblemSpace: ObservableObject {
var gameData: GameData
init(gameData: GameData) {
self.gameData = gameData
}
var maxValue: Int {
set { self.gameData.maxValue = newValue }
get { return self.gameData.maxValue }
}
var player: String {
set { self.gameData.player = newValue }
get { return self.gameData.player }
}
}
class StatsManager: ObservableObject {
var gameData: GameData
init(gameData: GameData) {
self.gameData = gameData
}
var maxValue: Int {
set { self.gameData.maxValue = newValue }
get { return self.gameData.maxValue }
}
var player: String {
set { self.gameData.player = newValue }
get { return self.gameData.player }
}
}
struct CombineTestView: View {
@EnvironmentObject var gameData: GameData
@ObservedObject var problemSpace: ProblemSpace
@ObservedObject var statsManager: StatsManager
var body: some View {
VStack {
CombineTestPickerView(problemSpace: problemSpace,
statsManager: statsManager)
.environmentObject(gameData)
Spacer()
Text("GameData Player is: \(gameData.player), Max is \(gameData.maxValue)")
.font(.system(.title))
}
}
}
struct CombineTestPickerView: View {
@EnvironmentObject var gameData: GameData
@ObservedObject var problemSpace: ProblemSpace
@ObservedObject var statsManager: StatsManager
var body: some View {
VStack{
Picker(selection: $problemSpace.player,
label: Text(verbatim: "Selected name: \(problemSpace.player)")) {
ForEach(["Sam", "Gandalf", "Gollum"], id: \.self) { name in
Text(name)
}
}
Text("Picker ProblemSpace Player: \(problemSpace.player)")
ShowStatsManager(statsManager: statsManager)
.environmentObject(gameData)
}
}
}
struct ShowStatsManager: View {
@EnvironmentObject var gameData: GameData
@ObservedObject var statsManager: StatsManager
var body: some View {
VStack {
Stepper("Set max: ", value: $statsManager.maxValue)
Text("ShowStatsManager Player: \(statsManager.player), Max: \(statsManager.maxValue)")
}
}
}
One thing I've noticed that I am a bit perplexed by is I must pass the EnvironmentObject to all three Views, otherwise changes in the child views don't propagate back to the "source of truth" part of the model.
Do I have the right idea here? Is this the right way to use the SwiftUI API to solve this model consistency goal?
Thanks for your time and any thoughts!