Swift通过 @EnvironmentObjec共享和传递数据
Swift通过 @EnvironmentObjec共享和传递数据

Swift通过 @EnvironmentObjec共享和传递数据

本篇可以视为《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/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注