EnvironmentValues

Everything surrounding SwiftUI's Environment values comes from EnvironmentValues.

EnvironmentValues is a struct defined within the SwiftUI SDK containing all the current modified environment values. Let's take a deeper look.

This is the fourth entry of the Five Stars SwiftUI's Environment series. It's recommended to read the first three entries before proceeding: 1, 2, 3.

EnvironmentValues

This is EnvironmentValues's entire definition:

public struct EnvironmentValues {
  public init()
  public subscript<K: EnvironmentKey>(key: K.Type) -> K.Value
}

Debugging properties have been omitted for readability's sake.

While not immediately apparent, it's important to note that the subscript is read-write, thus allowing both reading and modifying environment values.
The implementation would look like:

public struct EnvironmentValues {
  public init() { 
    ...
  }

  public subscript<K: EnvironmentKey>(key: K.Type) -> K.Value {
    get {
      ...
    }
    set(newValue) { // πŸ‘ˆπŸ»
      ...
    }
  }
}

EnvironmentKey

EnvironmentValues relies on a second definition, EnvironmentKey, which is a generic protocol defined as:

public protocol EnvironmentKey {
  associatedtype Value
  static var defaultValue: Self.Value { get }
}

This EnvironmentKey:

  • lets us define the type of each environment value (via associatedtype)
  • helps SwiftUI identify the storage for any environment value
  • allows us to assign a default value for each key (more on this later)

A dictionary

We can think of EnvironmentValues as a Swift dictionary wrapper, where:

  • the dictionary type is along the lines of [EnvironmentKey: Any]
  • each EnvironmentKey key will have an associated value of type EnvironmentKey.Value
  • the dictionary will only contain all the values that have been explicitly set for that specific view (and its ancestors)

Every view has its own, separate, EnvironmentValues instance. When we say that environment values are propagated through the view hierarchy, what really happens behind the scenes is that (a copy of) this dictionary is passed down from parent to child.

This EnvironmentValues "dictionary" is not really a Swift dictionary but a private PropertyList type. Regardless, it helps thinking about it as a dictionary.

Example

Let's make an example where we focus only on the environment values that we explicitly set.
Consider the following view:

VStack {
  ViewA()
    .foregroundColor(.red) // πŸ”΄
  ViewB()
}

If we generate a high-level view hierarchy, we will have:

VStack
β”œβ”€β”€ ViewA
└── ViewB

Since we don't set any environment value on VStack, its EnvironmentValues dictionary will be empty, [:].

We also don't set any environment value on ViewB; ViewB will inherit the same dictionary as VStack, [:].

On the other hand, we set the foregroundColor on ViewA:
ViewA will inherit VStack's dictionary, [:], and add its foregroundColor on top, ending up with a dictionary similar to [ForegroundColorKey: Color.red].

VStack      // Environment dictionary β†’ [:]
β”œβ”€β”€ ViewA   // Environment dictionary β†’ [ForegroundColorKey: πŸ”΄], VStack + foregroundColor modifier
└── ViewB   // Environment dictionary β†’ [:], inherited from VStack

Since every environment value comes with a default value, if a view tries to get a value that has not been explicitly set, EnvironmentValues will return the EnvironmentKey.defaultValue for the associated key.

All the above is true for simple environment keys/values: we will see more advanced use cases in the following article of the series.

Dumping EnvironmentValues's dictionary

In the previous chapter, we ignored values that we hadn't explicitly set ourselves. However, a real EnvironmentValues dictionary has plenty of values coming from system settings, device characteristics, and all other values set on ancestor views.

Even when we don't set environment values ourselves, any view has an associated EnvironmentValues dictionary with 40+ environment values.
These values are not necessarily part of the public API, but are values that SwiftUI uses to determine each view's context, thus deciding how/what to draw.

For a sneak peek into the associated dictionary of any view, Chris Eidhof has a handy DumpingEnvironment code snippet:

struct DumpingEnvironment<V: View>: View {
  @Environment(\.self) var env
  let content: V

  var body: some View {
    dump(env)
    return content
  }
}

DumpingEnvironment wraps any view we're interested in, and prints in the debugger the associated EnvironmentValues instance (via Swift's dump(_:name:indent:maxDepth:maxItems:)).

Usage example:

DumpingEnvironment(content: ContentView())

Note that DumpingEnvironment is a View with its entire environment as a dependency. Make sure to use this only for debugging purposes.

Conclusions

As app developers, we don't directly interact with EnvironmentValues often. Instead, most interactions are done via proxies such as the @Environment property wrapper and the environment(_:,_:) view modifier.

In the next article, we will tie all of them together to get the bigger picture:
make sure to subscribe via feed RSS or follow @FiveStarsBlog on Twitter.

Thank you for reading!

Questions? Feedback? Feel free to reach out via email or Twitter!

β­‘β­‘β­‘β­‘β­‘

Further Reading

Explore SwiftUI

Browse all