SwiftUI:观察@Environment 属性变化
Posted
技术标签:
【中文标题】SwiftUI:观察@Environment 属性变化【英文标题】:SwiftUI: observe @Environment property changes 【发布时间】:2020-01-16 15:48:02 【问题描述】:我试图使用 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。我确定我在这里遗漏了一些东西,这是怎么回事?谢谢。
【问题讨论】:
我很困惑,为什么使用环境与环境对象?您可以使用它和发布商 @ColinWhootenEnvironmentObject
用于在视图层次结构中注入对象,并使这些对象在视图层次结构内的所有视图中可用。 Environment
与视图没有严格的关系。它只是在您的项目中创建一个全局依赖项。当您创建 Environment
依赖项时,您可以从应用程序中的所有实体(不仅仅是视图)访问该依赖项。所以,我想知道我是否可以在Environment
对象中观察Published
属性。
我遇到了同样的问题,但我使用的是EnvironmentObject
,里面有ObservedObject
。除非我将 ObservedObject
更改为 Published
,否则视图不会更新。谢谢你的提示
【参考方案1】:
Environment
让您可以访问存储在EnvironmentKey
下的内容,但不会为其内部生成观察者(即,如果 EnvironmentKey 的值自身发生变化,您会收到通知,但在您的情况下,它是实例并且其引用已存储下键不变)。所以它需要手动观察,你那里有发布者吗,如下所示
@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 如何通过colorScheme
、horizontalSizeClass
等活动实现这一目标?【参考方案2】:
对于 Apple 如何准确地动态发送更新到其标准 Environment
键(colorScheme
、horizontalSizeClass
等),我没有明确的答案,但我确实有一个解决方案,我怀疑 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
键/值。这是现有代码之间的第一个区别。 步骤 1 中的每个 @Published
属性将有一个 EnvironmentKey
,而不是使用 IntGenerator
。
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 中工作/测试
【讨论】:
以上是关于SwiftUI:观察@Environment 属性变化的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:属性装饰器的理解@State,@Binding,@ObservedObject,@Published,@Environment,@EnvironmentObject
调用 @Environment 时收到错误“类 'Environment' 不能用作属性”
SwiftUI:@Environment 未在视图层次结构中接收提供的值