SwiftUI Custom Environment Values

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)

Hello, world! caption with yellow background

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)
  }
}