@Environment in SwiftUI is a property wrapper for reading values from the system environment, automatically distributed through the View hierarchy. The component provides access to color scheme, locale, font size, managedObjectContext, and dozens of other system parameters. According to Apple Developer Documentation (2025), @Environment guarantees that any change in an environment value triggers a redraw of all subscribed Views, providing reactive interface updates without manual calls.
Key Takeaways
@Environment is a SwiftUI property wrapper designed for reading values from the system environment. The environment is a hierarchical container of values that SwiftUI automatically distributes from parent Views to child Views. Each environment value is identified by a key — a type conforming to the EnvironmentKey protocol.
The environment mechanism resembles dependency injection at the framework level: the system provides a predefined set of values — color scheme (light/dark), locale, font size, managedObjectContext for Core Data, dismiss for closing screens, and many others. A View that declares @Environment with a specific key automatically receives the current value and redraws when it changes.
The SwiftUI environment architecture is based on the EnvironmentValues protocol — a structure containing all system values. Each value is stored as a property of this structure with a getter and setter. @Environment uses a key path to access a specific property: @Environment(\.colorScheme) — access to color scheme, @Environment(\.locale) — access to locale.
The @Environment property wrapper implements two key mechanisms: reading a value from the environment and subscribing to its changes. When a View is created, SwiftUI goes through all @Environment properties and links them to the corresponding values from the current context. If a parent View changes a value via the .environment() modifier, all child Views reading that value are automatically redrawn.
An important feature: @Environment supports optional values. If a value is not set in the hierarchy, the default value defined in EnvironmentKey is returned. For system keys, the default value is always reasonable — for example, the default color scheme is .light. For custom keys, the developer defines the default value in the defaultValue method of the EnvironmentKey protocol.
struct EnvironmentReaderView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.locale) var locale
@Environment(\.sizeCategory) var sizeCategory
var body: some View {
VStack {
Text("Current scheme: \(colorScheme == .dark ? "Dark" : "Light")")
Text("Locale: \(locale.identifier)")
Text("Font size: \(sizeCategory)")
}
}
}
In the example, the View reads three system environment values. When colorScheme changes — for instance, the user enabled dark mode in settings — the View automatically redraws with the new value. Similarly, when the region or font size (Dynamic Type) changes. The View does not need to subscribe to notifications or call refresh — SwiftUI manages this automatically.
SwiftUI provides dozens of system environment values covering various aspects of interface and behavior. Color scheme (\.colorScheme) is one of the most commonly used values, allowing the interface to adapt to light and dark themes. Locale (\.locale) contains the user’s regional settings for formatting dates, numbers, and currencies.
For Core Data, managedObjectContext (\.managedObjectContext) is used — a context passed through the environment from the persistence container. For navigation, dismiss (\.dismiss) is available for closing the current screen, and isPresented (\.isPresented) for modal presentations. For calendar and time zone — calendar and timeZone respectively.
| Key Path | Type | Purpose |
|---|---|---|
| \.colorScheme | ColorScheme | Light or dark theme |
| \.locale | Locale | Regional settings |
| \.sizeCategory | ContentSizeCategory | Dynamic Type font size |
| \.managedObjectContext | NSManagedObjectContext | Core Data context |
| \.dismiss | DismissAction | Close screen |
| \.calendar | Calendar | Current calendar |
| \.timeZone | TimeZone | Time zone |
| \.horizontalSizeClass | UserInterfaceSizeClass | Horizontal screen size |
To access system values, use the key path with a dot: @Environment(\.dismiss) var dismiss. The compiler checks the existence of the key path in EnvironmentValues, so an incorrect key will cause a compile-time error. New system values are added by Apple with each iOS version — the full list is available in the EnvironmentValues documentation.
Despite the similar names, @Environment and @EnvironmentObject serve different purposes. @Environment reads system or custom values registered via EnvironmentKey. @EnvironmentObject is a property wrapper for an ObservableObject passed through the environment by type, without an explicit key.
@EnvironmentObject is used for dependency injection: a parent View creates an object (e.g., ViewModel) and passes it to child Views via the .environmentObject() modifier. Child Views receive it through @EnvironmentObject and can both read and modify its properties. @Environment, on the other hand, is read-only for system values and does not support feedback.
| Parameter | @Environment | @EnvironmentObject |
|---|---|---|
| Purpose | System and custom values | ObservableObject injection |
| Key | EnvironmentValues key path | By object type |
| Write | Read-only | Read and write |
| Custom value | Via EnvironmentKey | Via ObservableObject class |
| Default value | Yes (defaultValue) | No (must be passed) |
In practice: use @Environment to access system parameters (theme, locale, font size) and custom configurations that don’t change at runtime. Use @EnvironmentObject to pass a ViewModel or service through the View hierarchy when state needs to be modified from child components.
Let’s look at creating a custom environment value. To do this, you need to define a structure conforming to the EnvironmentKey protocol and extend EnvironmentValues with a new property. This allows passing theme configuration or app settings through the entire View tree without props.
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 }
The EnvironmentKey protocol requires implementing the static defaultValue property — the value that will be used if the parent View has not set a custom environment. Extending EnvironmentValues adds a computed property appTheme using a subscript with the key. After this, any View can read the value via @Environment(\.appTheme).
struct ThemedView: View {
@Environment(\.appTheme) var appTheme
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
if appTheme == .dark || (appTheme == .system && colorScheme == .dark) {
Text("Dark mode active")
.foregroundStyle(.white)
.background(Color.black)
} else {
Text("Light mode active")
.foregroundStyle(.black)
.background(Color.white)
}
}
}
}
struct ContentView: View {
@State private var selectedTheme = AppTheme.system
var body: some View {
ThemedView()
.environment(\.appTheme, selectedTheme)
}
}
ThemedView reads two environments: the custom appTheme and the system colorScheme. The combination allows flexible theme configuration: the user can choose Light, Dark, or System theme. If System is selected, the value is taken from colorScheme, which automatically changes when the theme is toggled in iOS settings. The parent View (ContentView) sets the appTheme value via the .environment() modifier.
struct ModalView: View {
@Environment(\.dismiss) var dismiss
@State private var name = ""
var body: some View {
NavigationStack {
Form {
TextField("Your name", text: $name)
Button("Save") { dismiss() }
}
.navigationTitle("Edit Profile")
}
}
}
This example demonstrates practical use of dismiss — an instance of DismissAction from the environment. Calling dismiss() as a function closes the modal screen or pops the NavigationLink. The only requirement is that the View must be presented modally or be inside a NavigationStack. dismiss is automatically determined from the context: if the View is opened as a sheet — the sheet is closed, if as a popover — the popover is closed.
Frequently Asked Questions
No, @Environment is read-only. To change values, use @EnvironmentObject with ObservableObject or @Binding. Custom EnvironmentKeys can have a setter in the extension, but changing through it does not trigger UI updates — this is technically possible but not recommended.
@Binding creates a two-way connection to a source of truth (State, StateObject, ObservableObject). @Environment is a one-way read from the hierarchical context. @Binding is suitable for passing data to a child View, @Environment — for accessing system or global settings.
Define a structure implementing the EnvironmentKey protocol with a static defaultValue. Then extend EnvironmentValues with a property using getter/setter via subscript[key]. After registration, use @Environment(\.yourKey) for reading and .environment(\.yourKey, value) for setting.
SwiftUI provides over 50 system values: colorScheme, locale, sizeCategory, managedObjectContext, dismiss, calendar, timeZone, horizontalSizeClass, verticalSizeClass, accessibilityEnabled, layoutDirection, legibilityWeight and others. Full list in the EnvironmentValues documentation.
Yes, @Environment works in Preview, but default values may differ from the simulator. For testing in Preview, use the .environment() modifier directly in the Preview code: ThemedView().environment(\.colorScheme, .dark). This allows visually checking different environment states.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also