ios `SwiftUI` - `EnvironmentObject`s,`EnvironmentValues`不传播到模态呈现的`UIViewController`

mbjcgjjk  于 2023-07-01  发布在  iOS
关注(0)|答案(2)|浏览(135)

SwiftUIEnvironmentObject s和EnvironmentValues应该传播到孩子,如果视图层次设置正确-这似乎是真正的纯SwiftUI视图层次的情况。当父视图控制器实际上包含其子视图控制器时,与UIKit大小写混合也是如此(例如; UITabViewControllerUINavigationController,...)。
但是,在SwiftUI视图层次结构上以模态方式呈现的UIKit视图控制器并不从其父视图层次结构获取环境。这是我的设置。

// ContentView
struct ContentView: View {

    var body: some View {
        TabBarController()
            .environment(\.lineLimit, 3) // override default value of lineLimit
    }

}

// TabBarController
struct TabBarController: UIViewControllerRepresentable {

    typealias UIViewControllerType = UITabBarController

    func makeUIViewController(context: Context) -> UIViewControllerType {
        let uiViewController = UITabBarController()
        uiViewController.setViewControllers([
            UIHostingController(rootView: FooBar()),
            UIHostingController(rootView: FooBar()),
            UIHostingController(rootView: FooBar())
        ], animated: false)
        return uiViewController
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        // For test purpose, present view controller modally
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            uiViewController.present(UIHostingController(rootView: FooBar()), animated: true)
        }
    }

}

// FooBar
struct FooBar: View {

    @Environment(\.lineLimit) var lineLimit

    var body: some View {
        Text("Hello World!")
            .onAppear {
                print(lineLimit) // prints given environment value
            }
    }

}

TabBarController实现中,我设置了三个选项卡,它们的根视图为FooBar,它提供了一个模态视图控制器,其根视图为FooBarFooBar什么也不做,只是打印lineLimit的值,从它的EnvironmentValues出现。我将lineLimit设置为3以覆盖其默认值。选项卡组件打印3,而显示的视图打印nil(默认值)。
我做错了什么,还是被禁止了?如果是这样的话,那就太奇怪了,因为SwiftUI框架的sheet()确实能按预期工作(尽管我不能使用这个API,因为我需要完全控制UIViewController的表示来应用自定义转换)。

jgwigjjp

jgwigjjp1#

SwiftUI的EnvironmentObjectsEnvironmentValues应该传播ONLY到SwiftUI子视图。对于其他(跨框架/API)的情况,我们必须手动执行此操作,例如

struct TabBarController: UIViewControllerRepresentable {
    @Environment(\.lineLimit) var lineLimit                // << here !!

    typealias UIViewControllerType = UITabBarController

    func makeUIViewController(context: Context) -> UIViewControllerType {
        let uiViewController = UITabBarController()
        uiViewController.setViewControllers([
            UIHostingController(rootView: 
                FooBar().environment(\.lineLimit, lineLimit)),   // << here !!
            UIHostingController(rootView: FooBar()),
            UIHostingController(rootView: FooBar())
        ], animated: false)
        return uiViewController
    }

    // ... other code
}
xxls0lw8

xxls0lw82#

理想情况下,UIHostingController.init可以选择接受Context(沿着它的UIViewControllerRepresentableContext),然后将整个环境进一步传递给所呈现的SwiftUI视图。
可悲的是,事实并非如此。一种可能的解决方案可以避免现有的不可扩展的解决方案,即手动逐个键地传递所有必要的环境值(有时在模块化架构中甚至不可能):
1.创建一个自定义的 Package 器来封装所有的环境值:

struct EnvironmentValuesKey: EnvironmentKey {
    static var defaultValue: EnvironmentValues? = nil
}

public extension EnvironmentValues {

    var environmentValues: EnvironmentValues? {
        get { self[EnvironmentValuesKey.self] }
        set { self[EnvironmentValuesKey.self] = newValue }
    }
}

1.将其馈送到应呈现的UIHostingController

UIHostingController(
  rootView: FooBar()
    .environment(\.environmentValues, context.environment)  // <- HERE

1.在目标SwiftUI视图中使用它,可以动态地:

// Source: Regular SwiftUI
@Environment(\.lineLimit) var lineLimit
// Source: UIKit presentation
@Environment(\.environmentValues?.lineLimit) var presentedLineLimit

... 

.lineLimit(presentedLineLimit ?? lineLimit)

或者,假设这个视图总是只从UIKit中呈现

@Environment(\.environmentValues?.lineLimit) var lineLimit

相关问题