Blog

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 to foregroundColor simply sets the foreground color of the text
  • On View (to which our wrapper conforms), the call to foregroundColor 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.


  • See the Swift Talk Collection

Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts