首页 > 解决方案 > SwiftUI:@Environment 未在视图层次结构中接收提供的值

问题描述

我正在按照这个项目的示例创建我的 iOS 应用程序(感谢 Alexey!),但无法让 @Environment 变量接收正在向下传递 UI 层次结构的值。顶级视图接收正确的值,但下游视图接收该default值。

编辑:绑定复制 Asperi 的代码后,我发现这种行为仅在通过NavigationLink. 更新了以下代码:

EDIT2:问题environment在于调用方法的位置。调用它NavigationView而不是MainView解决问题。代码更新如下:

自定义环境键 -DIContainer

struct DIContainer: EnvironmentKey {
    
    let interactor: Interactor
    
    init(interactor: Interactor) {
        self.interactor = interactor
    }
    
    static var defaultValue: Self { Self.default }
    
    private static let `default` = Self(interactor: .stub)
}

extension EnvironmentValues {
    var injected: DIContainer {
        get { self[DIContainer.self] }
        set { self[DIContainer.self] = newValue }
    }
}

App结构

private let container: DIContainer

init() {
    container = DIContainer(interactor: RealInteractor())
}

var body: some Scene {
    WindowGroup {
        NavigationView {
            MainView()
        }
        .environment(\.injected, container)
}

主视图

struct MainView: View {

    @Environment(\.injected) private var injected: DIContainer
    // `injected` has the `RealInteractor`, as expected

    var body: some View {
        VStack {
            Text("Main: \(injected.foo())")  \\ << Prints REAL

            NavigationLink(destination: SearchView()) {
                Text("Search")
            }
        }
    }
}

搜索视图

struct SearchView: View {

    @Environment(\.injected) private var injected: DIContainer
    // `injected` has the `StubInteractor`, why?

    var body: some View {
        Text("Search: \(injected.foo())")
    }
}

我可以通过如下修改来解决这个问题MainView

    var body: some View {
        SearchView()
            .environment(\.injected, container)
    }

但是避免重复这样做的目的不是@Environment吗?

任何指导/指针表示赞赏。

标签: swiftuienvironment

解决方案


我已经尝试复制所有部分并编译它们......结果就像预期的那样工作 - 环境在视图层次结构中向下传递,因此您可能会错过真实代码中的某些内容。

这是完整的模块,使用 Xcode 12.4 / iOS 14.4 测试

class Interactor {                    // << replicated !!
    static let stub = Interactor()
    func foo() -> String { "stub" }
}

class RealInteractor: Interactor {             // << replicated !!
    override func foo() -> String { "real" }
}

struct ContentView: View {                  // << replicated !!
    private let container: DIContainer

    init() {
        container = DIContainer(interactor: RealInteractor())
    }

    var body: some View {
        NavigationView {
            MainView()
        }
        .environment(\.injected, container) // << to affect any links !!
    }
}

// no changes in env parts
struct DIContainer: EnvironmentKey {

    let interactor: Interactor

    init(interactor: Interactor) {
        self.interactor = interactor
    }

    static var defaultValue: Self { Self.default }

    private static let `default` = Self(interactor: .stub)
}

extension EnvironmentValues {
    var injected: DIContainer {
        get { self[DIContainer.self] }
        set { self[DIContainer.self] = newValue }
    }
}

struct MainView: View {

    @Environment(\.injected) private var injected: DIContainer
    // `injected` has the `RealInteractor`, as expected

    var body: some View {
        SearchView()
    }
}


// just tested here
struct SearchView: View {

    @Environment(\.injected) private var injected: DIContainer

    var body: some View {
        Text("Result: \(injected.interactor.foo())")     // << works fine !!
    }
}

演示


推荐阅读