如何使用@EnvironmentObject
在视图之间共享数据
对于应该与整个应用程序中的所有视图共享的数据,SwiftUI
为我们提供了@EnvironmentObject
。这使我们可以在任何需要的地方共享模型数据,同时还可以确保我们在数据更改时自动保持视图更新。
将其@EnvironmentObject
视为@ObservedObject
在许多视图上使用的更智能,更简单的方法。而不是在视图A中创建一些数据,然后将其传递到视图B
,然后再视图C
,然后再使用视图D
,然后再最终使用它,您可以在视图中创建并将其放入环境中,以便视图B
,C
和D
将自动访问它。
注意:环境对象必须由祖先视图提供-如果SwiftUI
找不到正确类型的环境对象,则会崩溃。这也适用于预览,因此请小心。
例如,这是一个可观察的对象,用于存储用户设置:
class UserSettings: ObservableObject {
@Published var score = 0
}
是的,它只存储一个值,这没关系–重要的是,当值更改时,@Published
属性包装器将确保刷新所有使用该值的视图。
用户设置是一条明智的数据,我们可能希望在应用程序中的任何地方共享这些数据,因此我们不再需要手动进行同步。
因此,当我们的应用程序首次启动时,我们将创建一个实例,UserSettings
以便共享实例可以在应用程序中的任何位置访问。
注意:在xcode12之后,SceneDelegate.swift文件被移除了。
在WWDC20中,苹果为开发者带来了基于SwiftUI的全新项目模板。使用该模板,将使项目代码变得异常简洁、清晰。
@main
struct NewAllApp: App {
var body: some Scene {
WindowGroup {
Text("Hello world")
}
}
}
这里简单介绍下新的生命周期控制器:
App
SwiftUI2.0
提供的全新协议。通过声明一个符合App协议的结构来创建一个程序,并通过计算属性body
来实现程序的内容。
通过@main(swift5.3 新特性)
设定程序的入口,每个项目只能有一个进入点
管理整个app
的生命周期
在这个作用域下声明的常量、变量其生命周期与整个app
是完全一致的。
Scene
场景是视图(View)
层次结构的容器。通过在App
实例的body
中组合一个或多个符合Scene
协议的实例来呈现具体程序。
生命周期由系统管理
系统会根据运行平台的不同而调整场景的展示行为(比如相同的代码在iOS
和macOS
下的呈现不同,或者某些场景仅能运行于特定的平台)
SwiftUI2.0
提供了几个预置的场景,用户也可以自己编写符合Scene
协议的场景。上述代码中便是使用的一个预置场景WindowGroup
通过App
和Scene
的加入,绝不是仅仅减少代码量这么简单。通过这个明确的层级设定,我们可以更好的掌握在不同作用域下各个部分的生命周期、更精准数据传递、以及更便利的多平台代码共享。本文后面会用具体代码来逐个阐述。
App
和Scene
都是通过各自的functionBuilder
来解析的,也就是说,新的模板从程序的入口开始便是使用DSL
来描述的。
程序系统事件响应
由于去除了AppDelegate.swift
和SceneDelegate.swift
,SwiftUI2.0
提供了新的方法来让程序响应系统事件。
针对AppDelegate.swift
在iOS
系统下,通过使用@UIApplicationDelegateAdaptor
可以方便的实现之前AppDelegate.swfit
中提供的功能:
@main
struct NewAllApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
Text("Hello world")
}
}
}
class AppDelegate:NSObject,UIApplicationDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("launch")
return true
}
}
打开XXXApp.swift
文件:
1.按照上述实现方法,先添加AppDelegate
类,通过使用@UIApplicationDelegateAdaptor
可以方便的实现之前AppDelegate.swfit
中提供的功能,并建一个设置实例,并安全地存储它。现在回到我向您展示的那两行代码,并更改第二行,以便将我们的settings
属性ContentView
作为环境对象传递,如下所示:
import SwiftUI
@main
struct LandmarksApp: App {
@StateObject private var modelData = ModelData()
/// UIApplicationDelegateAdaptor:实现之前AppDelegate.swfit中提供的功能:
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appDelegate.settings)
}
}
}
class AppDelegate: NSObject,UIApplicationDelegate {
/// 建一个设置实例,并安全地存储它
var settings = UserSetings()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
}
完成后,共享UserSettings
实例可用于我们的内容视图及其承载或提供的任何其他视图。您需要做的就是使用@EnvironmentObject
属性包装器创建一个属性,如下所示:
不需要使用默认值初始化它,因为它将自动从环境中读取。
因此,我们可以构造一个ContentView
可增加分数设置的结构,甚至使其呈现一个DetailView
显示分数设置的结构,而无需创建或传递任何本地实例UserSettings
-它始终仅使用环境。
这是实现此目的的代码:
import SwiftUI
struct DetailView: View {
@EnvironmentObject var settings: UserSetings
var body: some View {
// A text view that reads from the environment settings
Text("Score: \(settings.score)")
}
}
struct ContentView: View {
@EnvironmentObject var settings: UserSetings
var body: some View {
NavigationView {
VStack {
Button(action: {
self.settings.score += 1
}, label: {
Text("Increase Score")
})
NavigationLink(
destination: DetailView()) {
Text("Show Detail View")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
因此,将对象注入环境后,您可以立即在顶层视图中或向下十层视图中开始使用它–没关系。最重要的是,每当任何视图更改环境时,所有依赖该视图的视图都会自动刷新,因此它们保持同步。
如您所见,我们不需要将UserSettings
场景委托中的实例与settings
我们两个视图中的属性显式关联– SwiftUI
自动发现它UserSettings
在环境中具有一个实例,因此就是被使用的实例。
警告:既然我们的视图依赖于当前的环境对象,那么还必须更新预览代码以提供一些示例设置来使用,这一点很重要。例如,ContentView().environmentObject(UserSettings())
应该使用诸如此类的预览功能。
参考:
https://zhuanlan.zhihu.com/p/152624613
https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views··