SwiftUI 中的 @Environment —— 它是什么、关键概念及机制

作者: IT Sectr 发布日期: 2026-06-25 阅读时间: 8 分钟

SwiftUI 中的 @Environment —— 用于从系统环境中读取值的属性包装器,这些值在 View 层次结构中自动传播。该组件提供对颜色方案、区域设置、字体大小、managedObjectContext 以及数十个其他系统参数的访问。根据 Apple Developer Documentation (2025)@Environment 确保环境值的任何更改都会导致所有订阅的 View 重新绘制,无需手动调用即可实现界面的响应式更新。

要点

  • @Environment —— 用于读取 SwiftUI 系统环境值的属性包装器
  • 颜色方案、区域设置和字体大小 —— 常用的系统值
  • 任何环境的更改都会导致订阅的 View 自动重新绘制
  • 自定义键允许通过 EnvironmentKey 创建自己的环境值
  • @Environment —— 只读;@EnvironmentObject —— 用于写入

SwiftUI 中的 @Environment 是什么?

@Environment —— 是一个 SwiftUI 属性包装器,用于从系统环境中读取值。环境是一个层次化的值容器,SwiftUI 自动从父 View 向子 View 传播。每个环境值由一个键标识 —— 一个符合 EnvironmentKey 协议的类型。

环境机制让人联想到框架级别的依赖注入:系统提供了一组预定义值 —— 颜色方案(浅色/深色)、区域设置、字体大小、用于 Core Data 的 managedObjectContext、用于关闭屏幕的 dismiss 以及许多其他值。声明了带有特定键的 @Environment 的 View 会自动接收当前值,并在其更改时重新绘制。

SwiftUI 环境的架构基于 EnvironmentValues 协议 —— 一个包含所有系统值的结构。每个值作为该结构的属性存储,带有 getter 和 setter。@Environment 使用 key path 访问特定属性:@Environment(\.colorScheme) —— 访问颜色方案,@Environment(\.locale) —— 访问区域设置。

@Environment 作为属性包装器

@Environment 属性包装器实现了两个关键机制:从环境读取值以及订阅其更改。在创建 View 时,SwiftUI 遍历所有 @Environment 属性并将它们与当前上下文中对应的值关联。如果父 View 通过 .environment() 修改器更改了值,所有读取该值的子 View 将自动重新绘制。

重要特性:@Environment 支持可选值。如果值未在层次结构中设置,则返回 EnvironmentKey 中定义的默认值。对于系统键,默认值总是合理的 —— 例如,默认颜色方案是 .light。对于自定义键,开发者在 EnvironmentKey 协议的 defaultValue 方法中自行定义默认值。

swift
struct EnvironmentReaderView: View {
    @Environment(\.colorScheme) var colorScheme
    @Environment(\.locale) var locale
    @Environment(\.sizeCategory) var sizeCategory

    var body: some View {
        VStack {
            Text("当前方案: \(colorScheme == .dark ? "Dark" : "Light")")
            Text("Locale: \(locale.identifier)")
            Text("字体大小: \(sizeCategory)")
        }
    }
}

在示例中,View 读取三个系统环境值。当 colorScheme 更改时 —— 例如,用户在设置中启用了深色模式 —— View 会自动使用新值重新绘制。当区域或字体大小(Dynamic Type)更改时也是如此。View 无需订阅通知或调用刷新 —— SwiftUI 自动管理这一切。

系统环境值

SwiftUI 提供了数十个系统环境值,涵盖界面和行为的各个方面。颜色方案(\.colorScheme)—— 最受欢迎的值之一,允许将界面调整为浅色和深色主题。区域设置(\.locale)包含用于格式化日期、数字和货币的用户区域设置。

对于 Core Data,使用 managedObjectContext(\.managedObjectContext)—— 通过环境从持久化容器传递的上下文。对于导航,dismiss(\.dismiss)用于关闭当前屏幕,isPresented(\.isPresented)用于模态视图。对于日历和时区 —— calendar 和 timeZone。

Key Path类型用途
\.colorSchemeColorScheme浅色或深色主题
\.localeLocale区域设置
\.sizeCategoryContentSizeCategoryDynamic Type 字体大小
\.managedObjectContextNSManagedObjectContextCore Data 上下文
\.dismissDismissAction关闭屏幕
\.calendarCalendar当前日历
\.timeZoneTimeZone时区
\.horizontalSizeClassUserInterfaceSizeClass屏幕水平尺寸

要访问系统值,请使用带点的 key path:@Environment(\.dismiss) var dismiss。编译器会检查 EnvironmentValues 中 key path 的存在性,因此错误的键将在编译阶段导致错误。Apple 在每个 iOS 版本中都会添加新的系统值 —— 当前列表可在 EnvironmentValues 文档中找到。

@Environment 与 @EnvironmentObject

尽管名称相似,@Environment@EnvironmentObject 解决不同的任务。@Environment 读取通过 EnvironmentKey 注册的系统或自定义值。@EnvironmentObject —— 是一个 ObservableObject 的属性包装器,通过环境按类型传递,无需显式键。

@EnvironmentObject 用于依赖注入:父 View 创建一个对象(例如 ViewModel)并通过 .environmentObject() 修改器将其传递给子 View。子 View 通过 @EnvironmentObject 接收它,并可以读取和修改其属性。而 @Environment —— 只读系统值,不支持反馈。

参数@Environment@EnvironmentObject
用途系统和自定义值ObservableObject 注入
EnvironmentValues key path按对象类型
写入只读读写
自定义值通过 EnvironmentKey通过 ObservableObject 类
默认值有(defaultValue)无(必须传递)

在实践中:使用 @Environment 访问系统参数(主题、区域设置、字体大小)和运行时不变的自定义配置。使用 @EnvironmentObject 在 View 层次结构中传递 ViewModel 或服务,当需要从子组件更改状态时。

@Environment 代码示例

让我们来看一下创建自定义环境值的过程。为此,需要定义一个符合 EnvironmentKey 协议的结构,并用新属性扩展 EnvironmentValues。这允许在没有 props 的情况下通过整个 View 树传递主题配置或应用程序设置。

swift
struct AppThemeKey: EnvironmentKey {
    static let defaultValue: AppTheme = .system
}

extension EnvironmentValues {
    var appTheme: AppTheme {
        get { self[AppThemeKey.self] }
        set { self[AppThemeKey.self] = newValue }
    }
}

enum AppTheme { case system, light, dark }

EnvironmentKey 协议要求实现静态属性 defaultValue —— 如果父 View 没有设置自定义环境时将使用的值。EnvironmentValues 扩展使用带键的 subscript 添加了计算属性 appTheme。之后,任何 View 都可以通过 @Environment(\.appTheme) 读取该值。

使用带有自定义键的 @Environment

swift
struct ThemedView: View {
    @Environment(\.appTheme) var appTheme
    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        VStack {
            if appTheme == .dark || (appTheme == .system && colorScheme == .dark) {
                Text("深色模式已激活")
                    .foregroundStyle(.white)
                    .background(Color.black)
            } else {
                Text("浅色模式已激活")
                    .foregroundStyle(.black)
                    .background(Color.white)
            }
        }
    }
}

struct ContentView: View {
    @State private var selectedTheme = AppTheme.system

    var body: some View {
        ThemedView()
            .environment(\.appTheme, selectedTheme)
    }
}

ThemedView 读取两个环境:自定义的 appTheme 和系统的 colorScheme。这种组合允许实现灵活的主题设置:用户可以选择“浅色”、“深色”或“系统”主题。如果选择了系统主题 —— 值从 colorScheme 获取,该值在 iOS 设置中切换主题时自动更改。父 View(ContentView)通过 .environment() 修改器设置 appTheme 值。

使用 dismiss 关闭屏幕

swift
struct ModalView: View {
    @Environment(\.dismiss) var dismiss
    @State private var name = ""

    var body: some View {
        NavigationStack {
            Form {
                TextField("Your name", text: $name)
                Button("保存") { dismiss() }
            }
            .navigationTitle("Edit Profile")
        }
    }
}

该示例演示了 dismiss —— 来自环境的 DismissAction 实例 —— 的实际使用。调用 dismiss() 作为函数会关闭模态屏幕或返回 NavigationLink。唯一的要求是 View 必须以模态方式呈现或位于 NavigationStack 内。dismiss 根据上下文自动确定:如果 View 作为 sheet 打开 —— 关闭 sheet,如果作为 popover —— 关闭 popover。

常见问题

可以从子 View 更改 @Environment 的值吗?

不可以,@Environment 仅用于读取。要更改值,请使用带有 ObservableObject 的 @EnvironmentObject 或 @Binding。自定义 EnvironmentKey 可以在扩展中具有 setter,但通过它进行的更改不会触发 UI 更新 —— 这在技术上是可行的,但不建议这样做。

@Environment 和 @Binding 有什么区别?

@Binding 与真相源(State、StateObject、ObservableObject)建立双向连接。@Environment —— 从层次化上下文中单向读取。@Binding 适合向子 View 传递数据,@Environment —— 适合访问系统或全局设置。

如何创建自己的环境值?

定义一个实现了带有 static defaultValue 的 EnvironmentKey 协议的结构。然后通过 subscript[key] 为 EnvironmentValues 添加带有 getter/setter 的属性扩展。注册后,使用 @Environment(\.yourKey) 进行读取,使用 .environment(\.yourKey, value) 进行设置。

SwiftUI 中有哪些环境值可用?

SwiftUI 提供了超过 50 个系统值:colorScheme、locale、sizeCategory、managedObjectContext、dismiss、calendar、timeZone、horizontalSizeClass、verticalSizeClass、accessibilityEnabled、layoutDirection、legibilityWeight 等。完整列表在 EnvironmentValues 文档中。

@Environment 在 Preview 中有效吗?

是的,@Environment 在 Preview 中有效,但默认值可能与模拟器不同。要在 Preview 中测试,请直接在 Preview 代码中使用 .environment() 修改器:ThemedView().environment(\.colorScheme, .dark)。这允许直观地检查不同的环境状态。

总结

  • @Environment —— 用于通过 key path 读取 SwiftUI 系统环境值的属性包装器
  • 颜色方案、区域设置、字体大小和 managedObjectContext —— 最常用的系统值
  • 环境更改会导致所有订阅的 View 自动重新绘制
  • 自定义 EnvironmentKey 允许用全局设置扩展应用程序环境
  • @Environment —— 只读;@EnvironmentObject —— 用于可写入的 ObservableObject
  • .environment() 修改器为层次结构中的子 View 设置值
  • 使用 @Environment 处理系统参数,@EnvironmentObject 处理 ViewModel 和服务

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读