You don't need to pass it multiple times, you can remove all environmentObject except for the top one. That's the whole point of environmentObject (one caveat is that some modifiers spawn a new hierarchy like navigation, pop-over, action sheet, sheet, and you need to re-inject the envObj).
What you need is @EnvironmentObject. When gd changes, all views that has @EnvironmentObject var ...: GameData will be invalidated and updated in no particular order. So views like ShowStatesManager can be updated multiple times (once from its own invalidation, another from its parent).
Same goes with ObservedObject.
I notice that all GameData, ProblemSpace, and StatsManager provides the same set of values. It'd be better to use only one class, probably GameData.
I generally prefer that ObservableObject don't share too much values. If they provides the same value, use the same class.
If ProblemSpace and StatsManager don't share the value, it's better to just split them into two classes (without shared GameData).
If there are other reasons that requires one ObservableObject to refer to another, like how ProblemSpace refers to GameData. You can do it, but you need to link them properly, which is a hassle, and I'd still suggest that you split them into multiple classes, strengthening the notion of single Source of Truth.
Notice that, ObservableObject has objectWillChange publisher, this is what SwiftUI is listening to. Most of the time @Published will do the job for you, though not in this case. What you need, is to make sure that when GameData (which is the real source of truth) fires objectWillChange, ProblemSpace and StatsManager also fire their own objectWillChanges.
You can do this by having them subscribing to the source-of-truth objectWillChange
class ProblemSpace: ObservableObject {
var subscription: Any! // Need to keep this alive to keep listening
init() {
... // Other setup
// Not sure if there's a cleaner method, but this is more-or-less what you'll do.
subscription = gameData.objectWillChange.sink {
self.objectWillChange.send()
}
}
}
You can also add your custom logic to check if the data really change, and avoid firing unnecessary objectWillChange.
When designing ObservableObject, keep in mind that all views with @EnvironmentObject will be invalidated when it changes. If EnvironmentObject encompasses too many variables, a lot of views will be listening, making it costly to even change a single variable.
PS
I'd also suggest that you make EnvironmentObject and State private. You're not suppose to be able to access/initialize them from outside the View. It'd also help with auto-complete