Reputation: 9897
(Using Xcode 11 beta 7) is it possible to access custom EnvironmentObject of ancestors? If so how? And using unlimited depth?
Let's say we have many levels of depth in our navigation, e.g
UIScene -> MainView -> TabView (which contains a tab:) -> SettingsView -> AboutView -> AppVersionView
Starting counting MainView as depth level 1, and not coubting TabView, our AppVersionView is at depth level 4 in our navigationstack.
Let's say we need to use some custom dependency, e.g. a RESTClient or whatever in AppVersionView. What this depedency is is irrelevant. What is relevant is that our SceneDelegate class instantiates this depedency and the view at depth 1 (MainView) declares it as an @EnvirontmentObject
to get it injected.
My current solution is that MainView "manually" forwards it by injecting it down to the next view in the stack, the SettingsView. And in the SettingsView I manually forward it again etc.
This is a simplified example, in fact I have multiple dependencies throughout my SwiftUI app, being used and different layers throughout the navigation stack.
My thought was if it is possible to read/access these injected EnvironmentObjects somehow? In best case scenario recursively from any earlier ancestor.
If I have understood SwiftUI correctly, that is the case with ViewModifiers. If I add the ViewModifier .foregroundColor(.red)
to MainView, it should be passed as a system wide default color through out the app (inherited).
I was hoping the same thing could be done with custom EnvironmentObject. And I know we can access predefined (non-custom) EnvironmentValues, in our views like this example from Mecid's great blog:
struct ButtonsView: View {
@Environment(\.sizeCategory) var sizeCategory
var body: some View {
Group {
if sizeCategory == .accessibilityExtraExtraExtraLarge {
VStack {
buttons
}
} else {
HStack {
buttons
}
}
}
}
}
Upvotes: 0
Views: 1357
Reputation: 385710
An object put into the environment using the environmentObject
modifier is passed down the view hierarchy, through TabView
and NavigationView
, to all descendant views. You don't have to do anything special (like manually forwarding it) to make this happen.
In this example, in SceneDelegate
, I'll store a model object in the environment of the MainView
using the environmentObject
modifier. The model object has an appVersion
string property. I'll access the model object using the @EnvironmentObject
attribute in AppVersionView
and use the model's appVersion
to populate a Text
subview.
Here's the code:
import UIKit
import SwiftUI
class Model: ObservableObject {
@Published var appVersion: String = "version-1"
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let model = Model()
let contentView = MainView().environmentObject(model)
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
struct MainView: View {
var body: some View {
TabView {
NavigationView {
SettingsView()
}
.tabItem {
Image(systemName: "gear")
Text("Settings") }
}
}
}
struct SettingsView: View {
var body: some View {
List {
NavigationLink("About", destination: AboutView())
}
}
}
struct AboutView: View {
var body: some View {
List {
NavigationLink("App Version", destination: AppVersionView())
}
}
}
struct AppVersionView: View {
@EnvironmentObject var model: Model
var body: some View {
VStack {
Text("App Version View")
Text(model.appVersion)
}
.padding()
.border(Color.black)
}
}
Upvotes: 3