Here’s my quick guide to creating your own custom SwiftUI environment values for things like global app settings.
Using The SwiftUI Environment
SwiftUI makes frequent use of the environment to pass data values down the view hierarchy. The EnvironmentValues
structure contains a long list of useful app settings that you can read using the Environment
property wrapper:
struct ContentView: View {
@Environment(\.sizeCategory) var sizeCategory
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.scenePhase) var scenePhase
var body: some View { ... }
}
See the documentation for EnvironmentValues for the full list of properties. We’re not limited to the built-in environment properties. Let’s see how to add our own.
Creating A Custom Environment Value
To add our own custom values to the environment we need to create an EnvironmentKey
and then extend EnvironmentValues
with our own environment property. Let’s revisit the Caption
view modifier I used to show SwiftUI Custom View Modifiers. The modifier accepts a font and background color:
Text("Hello, world!")
.caption(font: .headline, backgroundColor: .yellow)
That works fine but I want to set a default background color for an entire app and maybe override it for a view. Let’s add a property to the environment for the caption background color:
1. Create the environment key
We use the environment key to set and retrieve our custom property. The EnvironmentKey
protocol requires a default value for the property:
private struct CaptionColorKey: EnvironmentKey {
static let defaultValue = Color(.secondarySystemBackground)
}
Note that we can make the key private as we access the environment using key paths.
2. Extend the environment
To add our custom property to the environment we extend EnvironmentValues
providing a getter/setter that use our environment key:
extension EnvironmentValues {
var captionBackgroundColor: Color {
get { self[CaptionColorKey.self] }
set { self[CaptionColorKey.self] = newValue }
}
}
3. Add a view modifier (optional)
This step is not required but it can be convenient to add a view modifier for our environment property. We can already set the environment property:
ContentView()
.environment(\.captionBackgroundColor, .yellow)
We can improve that by adding an extension on View
for our property:
extension View {
func captionBackgroundColor(_ color: Color) -> some View {
environment(\.captionBackgroundColor, color)
}
}
This gives us a more compact syntax for overriding the environment property:
ContentView()
.captionBackgroundColor(.yellow)
In use
Once you’ve defined the key, using it is the same as for any of the Apple supplied environment keys. Use the @Environment
property wrapper to get the value from the environment:
@Environment(\.captionBackgroundColor) var backgroundColor
My updated view modifier that now relies on the environment to set the background colour:
import SwiftUI
struct Caption: ViewModifier {
let font: Font
@Environment(\.captionBackgroundColor) var backgroundColor
func body(content: Content) -> some View {
content
.font(font)
.padding([.leading, .trailing], 5.0)
.background(backgroundColor)
.cornerRadius(5.0)
}
}
extension View {
func caption(font: Font = .caption) -> some View {
modifier(Caption(font: font))
}
}
In use, I can now set the color at the app level and override the environment to change the background color for a specific view:
Text("Hello, world!")
.caption(font: .largeTitle)
.captionBackgroundColor(.yellow)
Summary
A quick summary of the three steps:
// 1. Create the key with a default value
private struct CaptionColorKey: EnvironmentKey {
static let defaultValue = Color(.secondarySystemBackground)
}
// 2. Extend the environment with our property
extension EnvironmentValues {
var captionBackgroundColor: Color {
get { self[CaptionColorKey.self] }
set { self[CaptionColorKey.self] = newValue }
}
}
// 3. Optional convenience view modifier
extension View {
func captionBackgroundColor(_ color: Color) -> some View {
environment(\.captionBackgroundColor, color)
}
}