繁体   English   中英

SwiftUI:观察@Environment 属性变化

[英]SwiftUI: observe @Environment property changes

我试图使用 SwiftUI @Environment属性包装器,但我无法让它按预期工作。 请帮助我理解我做错了什么。

例如,我有一个每秒产生一次整数的对象:

class IntGenerator: ObservableObject {
    @Published var newValue = 0 {
        didSet {
            print(newValue)
        }
    }

    private var toCanc: AnyCancellable?

    init() {
        toCanc = Timer.TimerPublisher(interval: 1, runLoop: .main, mode: .default)
            .autoconnect()
            .map { _ in Int.random(in: 0..<1000) }
            .assign(to: \.newValue, on: self)
    }
}

该对象按预期工作,因为我可以看到控制台日志上生成的所有整数。 现在,假设我们希望这个对象是一个环境对象,可以从整个应用程序和任何人访问。 让我们创建相关的环境密钥:

struct IntGeneratorKey: EnvironmentKey {
    static let defaultValue = IntGenerator()
}

extension EnvironmentValues {
    var intGenerator: IntGenerator {
        get {
            return self[IntGeneratorKey.self]
        }
        set {
            self[IntGeneratorKey.self] = newValue
        }
    }
}

现在我可以像这样访问这个对象(例如从视图中):

struct TestView: View {
    @Environment(\.intGenerator) var intGenerator: IntGenerator

    var body: some View {
        Text("\(intGenerator.newValue)")
    }
}

不幸的是,尽管newValue@Published属性,但我没有收到有关该属性的任何更新,并且Text始终显示 0。我确定我在这里遗漏了一些东西,这是怎么回事? 谢谢。

Environment使您可以访问存储在EnvironmentKey下的内容,但不会为其内部生成观察者(即,如果 EnvironmentKey 的值自行更改,您将收到通知,但在您的情况下,它是实例,并且存储在 key 下的引用没有更改)。 所以它需要手动观察,你那里有发布者吗,如下所示

@Environment(\.intGenerator) var intGenerator: IntGenerator

@State private var value = 0
var body: some View {
    Text("\(value)")
        .onReceive(intGenerator.$newValue) { self.value = $0 }
}

并且所有的作品...用 Xcode 11.2 / iOS 13.2 测试

备份

对于 Apple 如何准确地动态发送更新到它的标准Environment键( colorSchemehorizontalSizeClass ​​ntalSizeClass 等),我没有明确的答案,但我确实有一个解决方案,我怀疑 Apple 在幕后做了类似的事情。

第一步)为您的值创建一个带有@Published属性的ObservableObject

class IntGenerator: ObservableObject {
    
    @Published var int = 0
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        Timer.TimerPublisher(interval: 1, runLoop: .main, mode: .default)
            .autoconnect()
            .map { _ in Int.random(in: 0..<1000) }
            .assign(to: \.int, on: self)
            .store(in: &cancellables)
    }
    
}

第二步)为您的属性创建自定义Environment键/值。 这是您现有代码之间的第一个区别。 而不是使用IntGenerator ,您将为来自step 1的每个单独的@Published属性提供一个EnvironmentKey

struct IntKey: EnvironmentKey {
    static let defaultValue = 0
}

extension EnvironmentValues {
    var int: Int {
        get {
            return self[IntKey.self]
        }
        set {
            self[IntKey.self] = newValue
        }
    }
}

第三步 - UIHostingController 方法)如果您使用 App Delegate 作为您的生命周期(也就是带有 Swift UI 功能的 UIKit 应用程序)。 这是当我们的@Published属性更改时我们如何能够动态更新我们的Views的秘诀。 这个简单的包装View将保留一个IntGenerator实例,并在我们的@Published属性值更改时更新我们的EnvironmentValues.int

struct DynamicEnvironmentView<T: View>: View {
    
    private let content: T
    @ObservedObject var intGenerator = IntGenerator()
    
    public init(content: T) {
        self.content = content
    }
    
    public var body: some View {
        content
            .environment(\.int, intGenerator.int)
    }
}

让我们通过创建自定义UIHostingController并利用我们的DynamicEnvironmentView轻松地将其应用于整个功能的视图层次结构。 该子类自动将您的内容包装在DynamicEnvironmentView中。

final class DynamicEnvironmentHostingController<T: View>: UIHostingController<DynamicEnvironmentView<T>> {
    
    public required init(rootView: T) {
        super.init(rootView: DynamicEnvironmentView(content: rootView))
    }
    
    @objc public required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

下面是我们如何使用新的DynamicHostingController

let contentView = ContentView()
window.rootViewController = DynamicEnvironmentHostingController(rootView: contentView)

第三步 - 纯 Swift UI 方法)如果您使用的是纯 Swift UI 应用程序。 在这个例子中,我们的App保留了对IntGenerator的引用,但您可以在此处使用不同的架构。

@main
struct MyApp: App {
    
    @ObservedObject var intGenerator = IntGenerator()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.int, intGenerator.int)
        }
    }
}

第四步)最后是我们如何在需要访问int的任何View中实际使用新的EnvironmentKey 每当我们的IntGenerator类上的 int 值更新时,此View将自动重建!

struct ContentView: View {
    
    @Environment(\.int) var int
    
    var body: some View {
        Text("My Int Value: \(int)")
    }
}

在 Xcode 12.2 上的 iOS 14 中工作/测试

这个修饰符对我有用

.onChange(of: propertyName) { newValue in
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM