Sajjon
Sajjon

Reputation: 9897

SwiftUI : possible to access ancestor custom `@EnvironmentObject`? If yes, how?

(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

Answers (1)

rob mayoff
rob mayoff

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.

enter image description here

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

Related Questions