本篇可以视为《Swift 从实战到理论:@publish 和@ObservedObject》知识延伸篇。
EnvironmentObject 是 SwiftUI 提供的一种属性包装器,用于在多个视图层次结构中共享和传递数据。它能够让一个对象在整个视图层次结构中被访问,而无需手动传递数据。这对于需要在应用程序的不同视图之间共享数据的情况非常有用。
EnvironmentObject 的特点
1、依赖注入: 使用 EnvironmentObject 可以让视图依赖一个共享的数据对象,而无需通过构造函数或 @Binding 手动传递数据。
2、自动更新: 如果 EnvironmentObject 中的数据发生更改,所有使用它的视图都会自动重新渲染。这和 @StateObject 或 @ObservedObject 的行为类似,但可以在多个视图层次结构中共享。
3、单向数据流: EnvironmentObject 保持了 SwiftUI 的单向数据流原则,数据从上层向下层传递,且视图会根据数据的变化自动更新。
使用方法
定义数据模型
首先,创建一个符合 ObservableObject 协议的类或结构体:
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Anonymous"
}
在上层视图中创建并注入
在应用程序的入口或者某个高层视图中,使用 .environmentObject() 将这个对象注入到视图层次结构中:
@main
struct MyApp: App {
@StateObject private var userSettings = UserSettings()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(userSettings) // 注入 UserSettings 对象
}
}
}
在子视图中使用 @EnvironmentObject
在需要访问这个对象的任何视图中,使用 @EnvironmentObject 声明来访问这个共享数据:
struct ProfileView: View {
@EnvironmentObject var userSettings: UserSettings
var body: some View {
Text("Username: \(userSettings.username)")
}
}
@EnvironmentObject 的工作原理
对象注入: 当你在上层视图中通过 .environmentObject() 注入一个 EnvironmentObject,这个对象就会被存储在视图的环境中。
视图访问: 所有子视图都可以通过 @EnvironmentObject 访问这个对象,SwiftUI 会自动从环境中找到匹配的对象实例,并将其传递给视图。
自动更新: 如果 ObservableObject 中的属性发生变化(例如被 @Published 标记的属性),SwiftUI 会自动更新所有依赖于这个对象的视图。
什么时候使用 @EnvironmentObject
全局状态管理: 如果有一些数据需要在多个视图之间共享,例如用户的设置、主题颜色、认证状态等,EnvironmentObject 是非常合适的选择。
减少手动数据传递: 当视图层次结构较深时,直接通过构造函数传递数据会让代码变得混乱。使用 @EnvironmentObject 可以简化数据共享,让代码更加简洁。
示例
struct ParentView: View {
@EnvironmentObject var userSettings: UserSettings
var body: some View {
VStack {
ProfileView() // 可以访问同一个 EnvironmentObject
SettingsView() // 也可以访问同一个 EnvironmentObject
}
}
}
在 ProfileView 和 SettingsView 中,你都可以使用 @EnvironmentObject var userSettings: UserSettings,这样它们共享同一个 UserSettings 对象,而无需手动传递。
传递多个@Environment
在 SwiftUI 中,可以使用多个 @EnvironmentObject 或 @Environment 来传递多个参数。SwiftUI 支持同时将多个对象注入到视图环境中,每个对象都可以被独立访问。
假设你有多个需要共享的数据对象,比如用户设置 (UserSettings) 和应用主题 (AppTheme)。
定义多个 ObservableObject 类
import SwiftUI
class UserSettings: ObservableObject {
@Published var username: String = "Anonymous"
@Published var isLoggedIn: Bool = false
}
class AppTheme: ObservableObject {
@Published var isDarkMode: Bool = false
}
在顶层视图中注入多个对象
在顶层视图(比如应用入口的 App 或者某个视图容器)中,使用 .environmentObject() 注入多个对象:
@main
struct MyApp: App {
@StateObject private var userSettings = UserSettings()
@StateObject private var appTheme = AppTheme()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(userSettings) // 注入 UserSettings
.environmentObject(appTheme) // 注入 AppTheme
}
}
}
在子视图中使用多个 @EnvironmentObject
在子视图中,可以独立访问多个 @EnvironmentObject:
struct ProfileView: View {
@EnvironmentObject var userSettings: UserSettings
@EnvironmentObject var appTheme: AppTheme
var body: some View {
VStack {
Text("Username: \(userSettings.username)")
Toggle("Dark Mode", isOn: $appTheme.isDarkMode)
}
.padding()
}
}
在这个示例中,ProfileView 可以同时访问 UserSettings 和 AppTheme,并在视图中进行相关的数据绑定。
使用多个 @Environment
SwiftUI 也支持通过 @Environment 注入其他环境值(比如系统提供的 ColorScheme、Locale 等),这些值通常与应用的运行环境或系统设置相关。
例如,可以同时使用 @EnvironmentObject 和 @Environment:
struct SettingsView: View {
@EnvironmentObject var userSettings: UserSettings
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
Text("Current mode: \(colorScheme == .dark ? "Dark" : "Light")")
Toggle("Login Status", isOn: $userSettings.isLoggedIn)
}
.padding()
}
}
实际应用
真正学习和理解该知识还是需要通过实际的使用环境来学习并理解。
在开发《汇率仓库》应用时,我有一个UpdateER类,用于管理汇率数据:
class UpdateER:ObservableObject {
var updateERInfo: ForexDataStruct?
}
在ContentView主视图中,创建一个updateER实例并传入到HomeView和WarehouseView中:
struct ContentView: View {
@StateObject private var updateER = UpdateER()
var body: some View {
TabView(selection: $selectedTab) {
// 首页视图
HomeView(updateER: $updateER)
.tabItem {
Image(systemName: "chart.bar") // 对应图标
Text("Home") // 首页标题
}
.tag(0)
// 仓库视图
WarehouseView(updateER: $updateER)
.tabItem {
Image(systemName: "archivebox") // 对应图标
Text("Warehouse") // 仓库标题
}
.tag(1)
}
}
}
正常来讲,update实例更新数据时,下面的两个HomeView和WarehouseView也会同步更新数据,因为updateER实例已经将数据传入到这两个视图当中,实际上并不会同步更新数据。
这两个视图的时间分别是10月23日和10月16日。原因在于Swift中,视图不会自动检测到类实例的变化,除非使用合适的机制来通知它们数据的更新。即使在 UpdateER 类中更新了 updateERInfo,由于 UpdateER 不是一个 @Published 属性,SwiftUI 视图不会感知到它的变化。
要解决这个问题,就需要将 UpdateER 类中涉及数据更新的属性声明为 @Published,并确保在 ContentView 中使用 @StateObject 而不是 @State 来创建 UpdateER 实例。这样,视图会自动监听 updateERInfo 的更新并重新渲染。
因此就需要应用到@Published和@ObservedObject,以及@EnvironmentObject来传递。
class UpdateER: ObservableObject {
@Published var updateERInfo: ForexDataStruct?
}
首先需要让类遵循ObservableObject协议,以及将类中涉及数据更新的属性声明为@Published。
struct ContentView: View {
@StateObject private var updateER = UpdateER() // 使用 @StateObject 而不是 @State
...
}
在ContentView(视图部分)使用@StateObject来创建对应的实例,这样视图就可以自动监听数据的更新并重新渲染。
struct ContentView: View {
@StateObject private var updateER = UpdateER() // 使用 @StateObject
var body: some View {
TabView(selection: $selectedTab) {
// 首页视图
HomeView()
.tabItem {
Image(systemName: "chart.bar")
Text("Home")
}
.tag(0)
// 仓库视图
WarehouseView()
.tabItem {
Image(systemName: "archivebox")
Text("Warehouse")
}
.tag(1)
}
.environmentObject(updateER) // 通过 EnvironmentObject 注入
}
}
在视图中间通过.environmentObject将数据传递给子视图。
struct HomeView: View {
@EnvironmentObject var updateER: UpdateER // 使用 @EnvironmentObject
var body: some View {
// 使用 updateER 访问数据
Text("汇率信息: \(updateER.updateERInfo?.data.lastDate ?? "无")")
}
}
struct WarehouseView: View {
@EnvironmentObject var updateER: UpdateER // 使用 @EnvironmentObject
var body: some View {
// 使用 updateER 访问数据
Text("仓库数据: \(updateER.updateERInfo?.data.lastDate ?? "无")")
}
}
最后,两个子视图声明@EnvironmentObject来接受updateER,这样他们就可以自动相应UpdateER对象的更新了。
@EnvironmentObject:它使得 UpdateER 的实例可以在整个视图层次结构中使用,而不需要手动在每个层级传递。这种方式更适合那些需要在多个视图中访问相同数据的情况。
自动响应更新:因为 @EnvironmentObject 的数据发生变化时,所有依赖它的视图都会重新渲染,这确保了数据的同步和更新。
总结
@EnvironmentObject 是 SwiftUI 中的一种强大工具,用于在视图层次结构中共享数据。它可以管理应用程序的状态,并简化视图之间的数据传递。在正确使用的情况下,它可以极大地减少代码复杂度,并保持数据的一致性和同步。
多个 @EnvironmentObject: 在顶层视图中使用 .environmentObject() 注入多个对象。子视图中用 @EnvironmentObject 声明多个变量即可。
组合 @Environment 和 @EnvironmentObject: 可以在一个视图中使用多个 @Environment 和 @EnvironmentObject 来组合使用环境对象和系统提供的环境值。
简化共享数据的传递: 这种方式能够在视图层次结构中灵活、简洁地传递多个数据对象,让代码更易维护。
相关文章
Xcode报错:ObservedObject<UpdateER>.Wrapper’ to expected argument type ‘Binding<UpdateER>:https://fangjunyu.com/2024/10/23/xcode%e6%8a%a5%e9%94%99%ef%bc%9aobservedobject-wrapper-to-expected-argument-type-bindi/