SwiftUI:可以访问祖先自定义`@EnvironmentObject`?如果是,如何?

Posted

技术标签:

【中文标题】SwiftUI:可以访问祖先自定义`@EnvironmentObject`?如果是,如何?【英文标题】:SwiftUI : possible to access ancestor custom `@EnvironmentObject`? If yes, how? 【发布时间】:2019-08-29 20:29:03 【问题描述】:

(使用 Xcode 11 beta 7)是否可以访问祖先的自定义 EnvironmentObject?如果有怎么办?并使用无限深度?

假设我们的导航有很多层次的深度,例如

UIScene -> MainView -> TabView(其中包含一个选项卡:) -> SettingsView -> AboutView -> AppVersionView

开始将 MainView 计为深度级别 1,而不考虑 TabView,我们的 AppVersionView 在我们的导航堆栈中处于深度级别 4。

假设我们需要使用一些自定义依赖项,例如RESTClient 或 AppVersionView 中的任何内容。这种依赖是什么无关紧要。相关的是,我们的 SceneDelegate 类实例化了这种依赖关系,并且深度 1(MainView)的视图将其声明为 @EnvirontmentObject 以将其注入。

我当前的解决方案是 MainView 通过将其向下注入堆栈中的下一个视图 SettingsView 来“手动”转发它。在 SettingsView 中,我再次手动转发它等等。

这是一个简化的示例,实际上我在整个my SwiftUI app 中有多个依赖项,正在使用和整个导航堆栈中的不同层。

我的想法是是否有可能以某种方式读取/访问这些注入的 EnvironmentObjects?在最好的情况下,从任何早期的祖先递归。

如果我正确理解了 SwiftUI,那么 ViewModifiers 就是这种情况。如果我将 ViewModifier .foregroundColor(.red) 添加到 MainView,它应该作为系统范围的默认颜色通过应用程序(继承)。

我希望自定义 EnvironmentObject 也能做到同样的事情。而且我知道我们可以访问预定义(非自定义)EnvironmentValues,在我们的视图中,例如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
            
        
    

【问题讨论】:

【参考方案1】:

使用environmentObject 修饰符放入环境的对象通过TabViewNavigationView 向下传递到所有后代视图。您无需执行任何特殊操作(例如手动转发)即可实现此目的。

在此示例中,在SceneDelegate 中,我将使用environmentObject 修饰符将模型对象存储在MainView 的环境中。模型对象有一个appVersion 字符串属性。我将使用AppVersionView 中的@EnvironmentObject 属性访问模型对象,并使用模型的appVersion 填充Text 子视图。

代码如下:

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)
    

【讨论】:

是的,但是我使用 MVVM 并且每个视图都有一个被注入的 ViewModel。我的任何 ViewModel 都有 1-5 个依赖项。我希望 skippinh havinh 转发这些依赖项。起初我认为它与问题无关,但现在我明白它确实是,因为函数 func envirormentObjectView 协议的函数。 并且将所有这些依赖项注入到视图中,这与 MVVM 的想法几乎背道而驰...... 并且其中一些依赖项要到稍后才能创建,因此如果它们不是可选的,它们不能全部放入某个上帝 Depencendies 对象并被传递,这很难看... 但是谢谢你!答案是“它可以工作!”,这也是不使用 MVVM 的原因。 @Saijon,定义“注入”。你的意思是内存占用?如果做得好,一个环境对象只有一个实例。 (不是 byVal,是 byRef。)

以上是关于SwiftUI:可以访问祖先自定义`@EnvironmentObject`?如果是,如何?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中将自定义字体系列设置为整个应用程序的默认字体

是否可以创建导航祖先的自定义 jQuery 选择器?例如:closest 或 :parents 选择器

SwiftUI-自定义容器

如何在 SwiftUI 视图中更新核心数据信息

如何查看 SwiftUI 可以作为自定义字体提供的其他字体选项?

如何在 SwiftUI 中添加自定义容器视图