What is @Environment in SwiftUI
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.
You can easily support sarunw.com by checking out this sponsor.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
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.
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.
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
.
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
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.
You can easily support sarunw.com by checking out this sponsor.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
Releated Resources
Read more article about SwiftUI, Data, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
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 Buy me a coffee Tweet ShareCreate 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.
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.