What is @Environment in SwiftUI

⋅ 6 min read ⋅ SwiftUI Data

Table of Contents

What is @Environment

When writing an app, we might need to read a device setting or device environment to adjust our view or behavior to match user needs. Examples of these values are current device locale, device current calendar, color scheme (dark or light), and size class.

In UIKit, this information is scattered among classes, as you can see in the following example.

Value Class
Color scheme traitCollection.userInterfaceStyle
Locale Locale.current
Calendar Calendar.current
Horizontal Size Class traitCollection.horizontalSizeClass

In SwiftUI, most of these system-wide settings and variables are accessible via the @Environment property wrapper. You can see all the available options in EnvironmentValues.

How to use @Environment

To access environment values, you create a @Environment variable specifying a keypath to value you want to read and write.

In the following example, we declare four @Environment variables and print them in text views.

struct ContentView: View {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.locale) private var locale
@Environment(\.calendar) private var calendar
@Environment(\.horizontalSizeClass) private var horizontalSizeClass

var body: some View {
VStack {
Text(locale.description)
Text(calendar.description)
Text(horizontalSizeClass == .compact ? "Compact": "Regular")
Text(colorScheme == .dark ? "Dark mode" : "Light mode")
}.font(.headline)
}
}

Here is another example where we adapt our view based on the vertical size class. In an iPhone portrait mode where the vertical size class is .regular, I layout the views in VStack. In a landscape mode where the vertical size class is .compact, I change the layout to HStack.

struct ContentView: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass

var body: some View {
if verticalSizeClass == .regular {
VStack {
Text("Alice")
Text("Bob")
Text("John")
}
.font(.headline)
} else {
HStack {
Text("Alice")
Text("Bob")
Text("John")
}
.font(.headline)
}
}
}

By using @Environment, we can read and adapt to the changes of the environment value.

A view can listen and adapt to environment value changes.
A view can listen and adapt to environment value changes.

Override @Environment value

Every environment value has a default value. You don't have to set it before use. Any view can read this value by declaring an @Environment variable with a keypath to the value you want to observe. And if you need to change that, you can do that with minimal afford.

You can set or override some values using the environment(_:_:) view modifier.

To demonstrate that, let's create a color scheme sensitive view, which will change its text and background color according to the color scheme (.colorScheme).

struct ColorSchemeSensitiveView: View {
@Environment(\.colorScheme) private var colorScheme

var body: some View {
Text(colorScheme == .dark ? "Dark" : "Light")
.frame(width: 100, height: 100)
.foregroundColor(colorScheme == .light ? .white : .black)
.background(colorScheme == .light ? Color.black : Color.white)
.cornerRadius(50)
.font(.headline)
}
}

Let's put it into use. We put ColorSchemeSensitiveView into a VStack.

struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
}
}
}

Toggle between dark and light color scheme would result in a change of ColorSchemeSensitiveView appearance. As you can see, ColorSchemeSensitiveView can pick up the color scheme change without the need for its parent (VStack and ContentView) to do anything.

As mentioned before, you can set or override some values using the environment(_:_:) view modifier. In the following example, we override .colorScheme to always .dark.

struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
}
.environment(\.colorScheme, .dark)
}
}

We override the color scheme value to dark, which makes ColorSchemeSensitiveView present as dark appearance regardless of device preference.

ColorSchemeSensitiveView stays in dark appearance even though the device color scheme change
ColorSchemeSensitiveView stays in dark appearance even though the device color scheme change

Environment override priority

The environment(_:_:) modifier affects the given view, as well as that view’s descendant views. It has no effect outside the view hierarchy on which you call it.

If we set environment value in many ancestor views, the child's view will read its closest parent value.

Here is an example of ColorSchemeSensitiveView with different environment value override.

struct ContentView: View {
@Environment(\.colorScheme) private var colorScheme

var body: some View {
VStack(spacing: 20) {
ColorSchemeSensitiveView() // <1>
VStack(spacing: 20) {
ColorSchemeSensitiveView() // <2>
.environment(\.colorScheme, .light)
ColorSchemeSensitiveView() // <3>
}.environment(\.colorScheme, .dark)
}
}
}

<1> The first ColorSchemeSensitiveView doesn't have any environment value override, so it picks up the color scheme from the device.
<2> The second ColorSchemeSensitiveView has two environment override, one from its parent and one on its own. The one assigned to itself has higher priority, so ColorSchemeSensitiveView show as .ligth appearance.
<3> The third ColorSchemeSensitiveView get an environment override of .dark from its parent, VStack.

Light appearance

  • The first one will pick up the same color scheme as the device, which is light.
  • The second one getting override by its own .environment(\.colorScheme, .light) and show as light appearance.
  • The last one gets an environment override of dark from its parent, VStack.
Result in light appearance
Result in light appearance

Dark appearance

  • The first one will pick up the same color scheme as the device, which is dark.
  • The second one getting override by its own .environment(\.colorScheme, .light) and show as light appearance.
  • The last one gets an environment override of dark from its parent, VStack.
Result in dark appearance
Result in dark appearance

Result

Conclusion

@Environment is an excellent concept. It exposes an app-wide configuration to a view in a friendly way. It is a singleton, which I think is fine since the name expresses its intention that it is an environment variable. You should expect it to share across the app. Even though it acts in a singleton manner, it also provides a dependency injection capability, making it easy to test and adapt to your own needs.

Even though all the examples of environment value in this article are device settings, @Environment isn't limited to that kind of value. It also includes view values such as lineLimit and font. This makes the environment variable a very powerful concept.

Releated Resources


Read more article about SwiftUI, Data, or see all available topic

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Tweet Share
Previous
Create a list of views in SwiftUI using ForEach

Part 1 in the series "Building Lists and Navigation in SwiftUI". We visit the first building block of any list view, content, and how to create them.

Next
How to use ScrollView in SwiftUI

Part 2 in the series "Building Lists and Navigation in SwiftUI". We will explore a ScrollView, UIScrollView equivalent in SwiftUI.

← Home