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 typeEnvironmentKey.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 privatePropertyList
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!