← All daysDay 14 of 49 · Part 4: State and data flow
Day 14

The environment

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 16

The environment

The environment carries values down the view tree without your handing them through every initializer on the way. A view reads a value with @Environment(\.colorScheme). An ancestor sets a value for everything beneath it with .environment(\.key, value). The system supplies many values this way: the color scheme, the size class, the locale, an action that dismisses the current screen. You add your own with the @Entry macro in an extension of EnvironmentValues. You can also place a whole @Observable object in the environment with .environment(model) and read it with @Environment(Model.self).

Use the environment for things that many views need and few of them change: a theme, a service, the current user. Do not use it to avoid deciding who owns a piece of data. A parameter in an initializer is easier to follow and easier to preview.

Example
extension EnvironmentValues {
    @Entry var accent: Color = .orange         // a new environment value and its default
}

struct Root: View {
    var body: some View {
        Detail()
            .environment(\.accent, .purple)    // set it for everything below
    }
}

struct Detail: View {
    @Environment(\.accent) private var accent  // read it, at any depth

    var body: some View {
        Text("Hello").foregroundStyle(accent)
    }
}
Exercise

Define a custom environment value for an accent color with @Entry. Set it at the root of your app, read it three levels down, and override it for one subtree only.

After this lesson you should be able to answer
  1. How is reading a value from the environment different from receiving it through an initializer?
  2. How do you define a custom environment value?
  3. What happens when a view reads an @Observable object from the environment and no ancestor provided one?
Resources