SwiftUI

Environment Values

深入理解 SwiftUI 的 Environment Values,掌握数据在视图层级中的传递、读取与自定义方法。

Environment Values 是 SwiftUI 中用于在视图层级间向下传递数据的一种高效机制。它允许父视图将配置信息、样式设置或全局状态隐式地传递给所有子视图,而无需通过初始化参数逐层显式传递。

核心概念

  • EnvironmentValues: 一个包含所有环境值的容器结构体。SwiftUI 预定义了许多标准值(如 colorScheme, font, locale),同时也支持自定义扩展。
  • @Environment: 用于在视图中读取环境值的属性包装器。当环境值发生变化时,依赖该值的视图会自动刷新。
  • EnvironmentKey: 用于定义自定义环境值的键,必须遵循 EnvironmentKey 协议并提供默认值。
  • Modifier: 使用 .environment(_:_:) 或专用修饰符(如 .font(), .foregroundStyle())来设置环境值。

读取环境值

使用 @Environment 属性包装器,通过 KeyPath 读取特定的环境值。

import SwiftUI

struct ContentView: View {
    // 读取系统的配色方案
    @Environment(\.colorScheme) var colorScheme
    // 读取系统的语言环境
    @Environment(\.locale) var locale

    var body: some View {
        VStack {
            Text("当前模式: \(colorScheme == .dark ? "深色" : "浅色")")
            Text("当前语言: \(locale.identifier)")
        }
    }
}

设置环境值

1. 使用专用修饰符(推荐)

SwiftUI 为常用的环境值提供了专用的视图修饰符,代码更具可读性。

VStack {
    Text("Hello World")
}
.font(.title) // 设置字体环境值
.foregroundStyle(.blue) // 设置前景色环境值
.lineLimit(2) // 设置行数限制

2. 使用 .environment 修饰符

对于没有专用修饰符的环境值,或自定义环境值,使用 .environment(_:_:)

VStack {
    Text("Custom Environment")
}
.environment(\.layoutDirection, .rightToLeft) // 强制从右向左布局

3. 使用 .transformEnvironment 修饰符

当你需要基于父视图传递下来的环境值进行修改时,使用 .transformEnvironment

VStack {
    Text("Transformed Environment")
}
.transformEnvironment(\.font) { font in
    // 如果父视图设置了字体,将其改为等宽字体,否则使用默认等宽字体
    font = font?.monospaced()
}

自定义 Environment Values

使用 Entry 宏 (iOS 18+, Swift 6)

在最新的 Swift 版本中,可以使用 @Entry 宏大大简化自定义环境值的定义。

import SwiftUI

extension EnvironmentValues {
    // 定义一个名为 themeColor 的自定义环境值,默认值为 .black
    @Entry var themeColor: Color = .black
}

struct CustomView: View {
    @Environment(\.themeColor) var themeColor

    var body: some View {
        Text("Theme Color")
            .foregroundStyle(themeColor)
    }
}

#Preview {
    CustomView()
        .environment(\.themeColor, .purple) // 注入自定义值
}

传统方式(EnvironmentKey)

在旧版本中,需要手动遵循 EnvironmentKey 协议。

private struct ThemeColorKey: EnvironmentKey {
    static let defaultValue: Color = .black
}

extension EnvironmentValues {
    var themeColor: Color {
        get { self[ThemeColorKey.self] }
        set { self[ThemeColorKey.self] = newValue }
    }
}

常用 Environment Values 分类

显示与布局(Display & Layout)

  • \​.colorScheme: 当前的配色方案(.light.dark)。
  • \​.pixelLength: 屏幕上一个物理像素对应的逻辑长度。
  • \​.displayScale: 当前屏幕的显示缩放因子(如 2.0, 3.0)。
  • \​.horizontalSizeClass: 水平方向的尺寸类别(.compact.regular)。
  • \​.verticalSizeClass: 垂直方向的尺寸类别。
  • \​.layoutDirection: 布局方向(.leftToRight.rightToLeft)。

文本与排版(Text & Typography)

  • \​.font: 当前的字体配置。
  • \​.lineLimit: 文本的最大行数限制。
  • \​.minimumScaleFactor: 文本缩小的最小比例。
  • \​.multilineTextAlignment: 多行文本的对齐方式。
  • \​.truncationMode: 文本截断模式(头部、中间、尾部)。
  • \​.lineSpacing: 行间距。
  • \​.allowsTightening: 是否允许文本紧缩以适应空间。

交互与动作 (Actions)

SwiftUI 提供了一些作为函数的环境值,用于执行特定动作。

  • \​.openURL: 打开一个 URL。
    @Environment(\.openURL) var openURL
    // 调用: openURL(URL(string: "https://apple.com")!)
  • \​.dismiss: 关闭当前呈现的视图(如 Sheet 或 Popover)。
    @Environment(\.dismiss) var dismiss
    // 调用: dismiss()
  • \​.refresh: 触发刷新操作(配合 .refreshable 使用)。

控件状态 (Controls)

  • \​.isEnabled: 视图及其子视图是否处于启用状态。
  • \​.controlSize: 控件的尺寸规格(.mini, .small, .regular, .large, .extraLarge)。
  • \​.editMode: 当前的编辑模式状态(.active, .inactive)。

辅助功能 (Accessibility)

  • \​.accessibilityEnabled: 辅助功能是否开启。
  • \​.accessibilityReduceMotion: 用户是否开启了“减弱动态效果”。
  • \​.accessibilityDifferentiateWithoutColor: 是否开启了“不以颜色区分”。
  • \​.legibilityWeight: 字体粗细偏好(如 .bold)。

国际化与地区 (Locale & Region)

  • \​.locale: 当前的语言环境设置。
  • \​.calendar: 当前使用的日历。
  • \​.timeZone: 当前时区。

最佳实践

  1. 优先使用专用修饰符: 如 .font(.headline) 优于 .environment(\.font, .headline),前者更简洁且可能有内部优化。
  2. 层级传递: 环境值会一直向下传递,直到被子视图覆盖。利用这一特性可以轻松实现全局主题或配置。
  3. 避免过度使用: 虽然环境值很方便,但过度依赖会使视图的数据来源变得隐晦。对于紧密耦合的数据,仍建议使用初始化参数传递。
  4. 性能考量: 修改高层级的环境值会触发整个子树的重绘,应尽量在需要的最小范围内修改环境值。
在 GitHub 上编辑

上次更新于