[英]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
键( colorScheme
、 horizontalSizeClass
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.