先理解 @Environment 和 @EnvironmentObject 的区别
它们名字很像,但用途不同:
| @EnvironmentObject | @Environment | |
|---|---|---|
| 注入什么 | 你自定义的 ObservableObject | SwiftUI/系统预设的环境值 |
| key 是什么 | 类型本身 | KeyPath(.colorScheme, .locale 等) |
| 谁来注入 | 你自己调用 .environmentObject() | 系统自动注入,也可以覆盖 |
| 典型场景 | 业务数据(登录用户、购物车) | 外观设置(深色模式、字体大小、语言) |
读取系统环境值
SwiftUI 内置了大量环境值,通过 @Environment 可以直接读取:
struct ContentView: View {
// 读取系统的深色/浅色模式
@Environment(\.colorScheme) var colorScheme
// 读取系统语言/地区
@Environment(\.locale) var locale
// 读取无障碍:用户是否开启了"减少动态效果"
@Environment(\.accessibilityReduceMotion) var reduceMotion
// 读取当前字体大小级别(用户在系统设置里调的)
@Environment(\.dynamicTypeSize) var typeSize
var body: some View {
Text("你好")
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
不需要任何注入,系统自动把这些值放进环境里,你直接取就行。
覆盖环境值(为子树设置特定环境)
.environment() 可以覆盖子树的环境值,常用于 Preview 或局部样式控制:
struct ContentView: View {
var body: some View {
VStack {
NormalView()
// 给这个子树强制设置深色模式
DarkPreviewArea()
.environment(\.colorScheme, .dark)
// 给这个子树强制设置某种语言
LocalizedSection()
.environment(\.locale, Locale(identifier: "ja"))
}
}
}
自定义 EnvironmentKey(高级用法)
你也可以自己往 SwiftUI 的环境系统里注册新的 key:
// 第一步:定义一个 EnvironmentKey,声明类型和默认值
struct ThemeColorKey: EnvironmentKey {
static let defaultValue: Color = .blue
}
// 第二步:给 EnvironmentValues 扩展一个属性,方便用 KeyPath 访问
extension EnvironmentValues {
var themeColor: Color {
get { self[ThemeColorKey.self] }
set { self[ThemeColorKey.self] = newValue }
}
}
// 注入
ContentView()
.environment(\.themeColor, .red)
// 读取
struct MyButton: View {
@Environment(\.themeColor) var themeColor
var body: some View {
Button("点击") {}
.foregroundColor(themeColor)
}
}
@Environment 的本质
和 @EnvironmentObject 一样,底层也是一个按 key 查找的字典,
只不过 key 不是类型,而是 EnvironmentValues 上的 KeyPath。
@propertyWrapper
public struct Environment<Value> {
// \.colorScheme 这样的 KeyPath 就是 key
public init(_ keyPath: KeyPath<EnvironmentValues, Value>)
// 你读 colorScheme 的时候,实际上是从当前 View 所在的环境字典里查找
public var wrappedValue: Value { get }
}
使用 @Environment 时需要关心的问题
-
@Environment 是只读的:
wrappedValue只有 getter,你不能直接写colorScheme = .dark。想改变环境值只能通过.environment()修饰符在外层覆盖。 -
和 @EnvironmentObject 的选择:
- 如果是外观、系统设置、能力开关类的数据,优先考虑自定义
EnvironmentKey+@Environment,它有默认值,不会崩溃,更安全。 - 如果是业务数据、需要触发 UI 更新的对象,用
@EnvironmentObject。
- @Environment 本身不驱动刷新:它读取的是一个快照值,不是
ObservableObject,当环境值变化时 SwiftUI 会重新渲染 View,但你不能在@Environment的值上接 Combine 链。如果需要响应式监听,那是@EnvironmentObject的职责。