在SwiftUI中,视图是一个“状态的函数”:
View = f(State)
只要数据变化,SwiftUI会自动重新计算依赖该数据的视图。
如果某者状态不是局部的(比如用户偏好、主题、App配置),也不想一层层的参数传递,如果想要子视图访问这个状态,就可以使用 Environment。
什么是Environment(环境)?
Environment是SwiftUI提供的一种依赖注入机制(Dependency Injection),允许上层视图将某个值放到一个全局的字典中。
所有子视图都可以读取这个值,而不用显式地传递参数。

这个字典在内部叫做 EnvironmentValues。
可以把它理解为:
struct EnvironmentValues {
var colorScheme: ColorScheme
var locale: Locale
var openURL: OpenURLAction
// ...
// 还有单独注册的类型
}
@Environment工作方式
在SwiftUI中,当调用系统的环境变量时:
@Environment(\.colorScheme) var colorScheme
编译器会自动生成访问代码:
1、视图被创建时,SwiftUI会从当前环境树中找到对应 key 的值;
2、将该值注入到这个属性;
3、如果上层环境发生改变(如用户切换浅色/深色模式),SwiftUI会重新生成视图;
这是最常见的KeyPath环境值方式,适用于内置值或EnvironmentValues扩展。
从 Swift 5.9 / iOS 17开始,Apple引入了新的API:
.environment(MyManager.self, myManager)
@Environment(MyManager.self) var manager
这是“类型化环境注入”,不同于早期的keyPath模式。
SwiftUI内部会构建一个类似的泛型容器:
Dictionary<ObjectIdentifier, Any>
当视图调用 @Environment(MyManager.self)时,SwiftUI就在环境树中查找ObjectIdentifier(MyManager.self)对应的值。
这个机制与旧式的 @Environment(\.keyPath)并存。
Environment的生命周期与继承链
实例通常是由 @State 或 @StateObject 创建或持有的对象,SwiftUI 会管理它的生命周期。
@State private var sound = SoundManager.shared
在顶层视图中,通过 environment 或者 environmentObject 将实例注入到子视图的环境变量中。
@main
struct pigletApp: App {
@State private var sound = SoundManager.shared
var body: some Scene {
WindowGroup {
ContentView()
}
.environment(sound)
}
}
这样,子视图就可以通过 @Environment 读取注入的实例。
environment和environmentobject的使用区别在于:
在iOS 13+ 中,通过 @StateObject 创建的实例对象,只能使用 environmentobject 注入。
// 旧版本
@StateObject private var sound = SoundManager.shared
.environmentobject(sound)
在 iOS 17+中,通过 @State创建的实例对象,可以使用environment注入。
// 新版本
@State private var sound = SoundManager.shared
.environment(sound)
Environment注入到子视图后,子视图中的所有视图,都可以通过 @EnvironmentObject 获取该实例。

在子视图中,通过 @EnvironmentObject 获取该实例。
@EnvironmentObject var sound: SoundManager // iOS 13+ 需要 ObservableObject
或者使用
@Environment(SoundManager.self) var sound // iOS 17+
当实例注入到子视图中,所有的子视图都可以读取,即使是多层嵌套的子视图: ContentView > View1 > View2,View2仍然可以读取注入Content的环境变量。
总结
在子视图中,一共有三种读取环境变量的方法:
@Environment(\.colorScheme) var colorScheme // iOS 13+ 获取系统环境变量
@EnvironmentObject var sound: SoundManager // iOS 13+ 需要 ObservableObject
@Environment(SoundManager.self) var sound // iOS 17+
1、Environment()获取系统环境变量,可以访问SwiftUI内置的EnvironmentValues容器,并通过 KeyPath 获取值。适用于系统提供的环境变量(如colorScheme、locale、openURL)或自定义EnvironmentValues扩展。
2、EnvironmentObject 适用于 iOS 13+,旧的Combine系统,基于Combine + ObservableObject的环境注入机制:
目标类型必须配合ObservableObject使用;
内部依赖Combine的objectWillChange通知;
使用时必须由父视图的 .environmentObject() 注入。
class AppStorageManager: ObservableObject {
@Published var name = "Swift"
}
struct RootView: View {
@StateObject private var manager = AppStorageManager()
var body: some View {
ChildView()
.environmentObject(manager)
}
}
struct ChildView: View {
@EnvironmentObject var manager: AppStorageManager
var body: some View {
Text(manager.name)
}
}
如果目标类型不使用ObservableObject,@EnvironmentObject获取环境变量时报错:
Generic struct 'EnvironmentObject' requires that 'AppStorageManager' conform to 'ObservableObject'
3、Environmen(Type.self) 适用于 iOS 17+,这是Swift Observation框架(Swift 5.9)新加入的机制,完全不依赖Combine,也不需要 .environmentObject()。
目标类型必须标记 @Observable;
SwiftUI 自动跟踪属性访问,环境注入通过 .environment(_:_:)传递。
@Observable
class AppStorageManager {
var name = "Swift"
}
struct RootView: View {
var body: some View {
ChildView()
.environment(AppStorageManager(), for: AppStorageManager.self)
}
}
struct ChildView: View {
@Environment(AppStorageManager.self) var manager
var body: some View {
Text(manager.name)
}
}
EnvironmentObject和Environmen(Type.self) 在绑定对象方面存在一些区别。
EnvironmentObject可以直接绑定 Toggle 控件:
EnvironmentObject var appStorage: AppStorageManager
Toggle("",isOn: $appStorage.isModelConfigManager)
Environment(Type.self) 不支持直接绑定:
@Environment(AppStorageManager.self) var appStorage
Toggle("",isOn: $appStorage.isModelConfigManager) // 报错,Cannot find '$appStorage' in scope
这是因为Environment(Type.self)提供的是对象引用,而Toggle需要一个Binding<Bool>类型。
可以通过手动创建Binding:
@Environment(AppStorageManager.self) var appStorage
Toggle("", isOn: Binding(
get: { appStorage.isModelConfigManager },
set: { appStorage.isModelConfigManager = $0 }
))
因为SwiftUI 17向下兼容 Combine模型,所以如果类是 @Observable,那么既可以使用@Environment(Type.self),也可以使用 EnvironmentObject,这样就可以实现属性绑定。此外,在一个项目中,也可以混用这两种体系。
相关文章
1、SwiftUI状态管理机制@Observable和@Environment:https://fangjunyu.com/2024/12/23/swiftui%e7%8a%b6%e6%80%81%e7%ae%a1%e7%90%86%e6%9c%ba%e5%88%b6observable%e5%92%8cenvironment/
2、Swift environmentObject预览报错:https://fangjunyu.com/2024/10/24/swift-environmentobject%e9%a2%84%e8%a7%88%e6%8a%a5%e9%94%99/
3、Swift通过 @EnvironmentObjec共享和传递数据:https://fangjunyu.com/2024/10/23/swift%e9%80%9a%e8%bf%87-environmentobjec%e5%85%b1%e4%ba%ab%e5%92%8c%e4%bc%a0%e9%80%92%e6%95%b0%e6%8d%ae/
