SwiftUI: Setting Environment Values
One of the simplest things to do in SwiftUI is rendering a text with a color. Yet doing this can tell us a lot about how SwiftUI works under the hood.
For example, let's consider the following view hierarchy:
Text("Hello").foregroundColor(.red)
Simple enough. The type of this value is Text
. When we use dump
to print this value to the console, we get a Text
value that consists of the text storage and a modifier for the color.
Now let's wrap our Text
inside another view struct before we apply the foreground color:
struct Wrapper<V: View>: View {
let body: V
}
Wrapper(body: Text("Hello")).foregroundColor(.red)
When we use dump
again, we can see that the type has (radically) changed:
ModifiedContent<
Wrapper<Text>,
_EnvironmentKeyWritingModifier<
Optional<Color>>>
This change in type is fundamental to how SwiftUI works.
We can write .foregroundColor
on any view. In the example above, we used two different implementations of the same method:
-
On
Text
, the call toforegroundColor
simply sets the foreground color of the text -
On
View
(to which our wrapper conforms), the call toforegroundColor
adds a new layer around the current view. When rendered, this layer changes the environment by adding or modifying the foreground color.
The environment is a list of keys and values, which are passed down from the root view to all of its children. At each step during the rendering, views pass their environment to their children. At any point, views are also free to change the environment; for instance, we can pass a new default foreground color, as shown above.
During rendering, the Text
view needs to pick its foreground color. If no color is specified for the Text
directly, as in the second example, it gets the color from the environment.
This is a powerful concept, and it means that we can change default values for entire hierarchies. For example, we can write something like this:
VStack {
Text("Hello")
HStack {
Text("Beautiful")
Text("World")
}
}.font(.title)
Here, the VStack
receives an environment with a custom font, the "Hello" text receives its environment from the VStack
, and likewise for the HStack
and its labels.
As an interesting aside, it's possible to inspect the current environment for a view using the following wrapper:
struct DumpingEnvironment<V: View>: View {
@Environment(\.self) var env
let content: V
var body: some View {
dump(env)
return content
}
}
For example, you could dump the environment for a leaf node of the view above:
VStack {
Text("Hello")
HStack {
DumpingEnvironment(content: Text("Beautiful"))
Text("World")
}
}.font(.title)
When you run the code, it prints a huge list of keys and values, containing font sizes, colors, safe area insets, presentation mode, undo managers, accessibility properties, display properties, and much more.
If you've enjoyed this little insight, our weekly Swift Talk video series explores SwiftUI in more depth.
Learn with us as we experiment, become a Subscriber.