4.3_如何使用@EnvironmentObject在视图之间共享数据

1. 简介

对于应该与整个应用程序中所有视图共享的数据,SwiftUI 为我们提供了 @EnvironmentObject 。这使我们可以在任何需要的地方共享模型数据,同时确保当模型数据发生变化时,我们的视图自动保持更新。

可以将 @EnvironmentObject 看作是在多个视图中使用 @ObjectBinding 的一种更智能、更简单的方法。而不是在视图 A 中创建一些数据,然后将其传递给视图 B,然后查看视图 C,然后在最终使用数据之前查看视图 D,我们可以在视图中创建它并将其放入环境(environment)中,这样的话,视图B、C、D将会自动访问它。

注意: 环境对象必须由祖先视图(ancestor view)提供 – 如果 SwiftUI 找不到正确类型的环境对象,将会导致崩溃(crash),这也适用于预览,所以要小心。

2. 示例

例如: 这里有一个可绑定对象,用于存储用户设置:

import Combine
import SwiftUI
​
class UserSettings: BindableObject {
    
    var didChange = PassthroughSubject<Void, Never>()
    
    var score = 0 {
        didSet {
            didChange.send(())
        }
    }
}

是的,它只存储一个值,但是没关系 – 重要的是: 当值发生改变时,PassthroughSubject 会告诉所有使用它(这个值)的的视图进行刷新。

用户设置 是我们可能希望能够在应用程序的任何地方共享的一个合理的数据,这样我们就不再需要手动处理同步。

因此,当我们的应用程序首次启动时,我们将创建一个 UserSettings 的实例。以便在我们的应用程序中随处都可以访问共享实例。

如果打开 Scenedelegate.swift ,您将在 (_:willConnectTo:options:)方法:中找到这两行代码

let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView())

第二行代码创建我们的初始内容视图(ContentView)并将其显示到屏幕上。这就是我们需要传入我们创建的任何环境对象的地方,以便 SwiftUI 可以在 ContentView 以及它使用的任何其它视图中使用我们创建的环境对象。

首先,将其添加为 SceneDelegate 的一个属性:

var settings = UserSettings() 

这会创建一个 settings 实例,并安全地存储它。现在回到刚才展示的那两行代码,并更改第二行,以便将我们的 settings 属性作为环境对象传递给 ContentView,如下所示:

window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(settings))

完成后,共享的 UserSettings 实例可用于 ContentView 以及它承载(hosts)或展现(presents)的任何其它视图。你只需要使用 @EnvironmentObject 属性包装器创建一个属性,如下所示:

@EnvironmentObject var settings: UserSettings

这不需要使用默认值进行初始化,因为它将自动从环境中读取。

因此,我们可以创建一个 ContentView 结构来增加我们的分数设置,甚至使它呈现出一个显示分数设置的 Detailview,所有这些都不需要创建或传递任何 UserSettings 的本地实例 – 它总是使用环境。

以下是实现这一目标的代码:

struct ContentView : View {
    
    @EnvironmentObject var settings: UserSettings
    
    var body: some View {
        NavigationView {
            VStack {
                Button(action: {
                    self.settings.score += 1
                }) {
                    Text("Increase score")
                }
                
                NavigationButton(destination: DetailView()) {
                    Text("Show Detail View")
                }
            }
        }
    }
}
​
struct DetailView : View {
    
    @EnvironmentObject var settings: UserSettings
    
    var body: some View {
        // A text view that reads from the environment settings
        Text("Score: \(settings.score)")
    }
}

因此,一旦你将一个对象注入到环境中,你就可以立即在顶层视图中或者在下面的十层视图中使用它——这并不重要。最重要的是,每当任何视图改变环境时,依赖它的所有视图都会自动刷新,从而保持同步。

如你所见,我们不需要将场景代理(scene delegate)中的 UserSettings 实例与我们两个视图中的 settings 属性显式关联 - SwiftUI 会自动发现它在环境中有一个 UserSettings 实例,因此这就是要使用的实例。

警告:既然我们的视图依赖于存在的环境对象,那么您还必须更新预览代码以提供一些要使用的示例设置。例如,使用 ContentView().EnvironmentObject(UserSettings()) 进行预览应该可以做到这一点。

Avatar
M X
Mobile, Front-End Developer

My research interests include swift developing, python developing and go developing.

Related

Next
Previous
comments powered by Disqus