Adding one custom value to EnvironmentValues used to mean defining a private key type conforming to EnvironmentKey, giving it a static defaultValue, then extending EnvironmentValues with a computed property that reads and writes through that key. Every custom value repeated the same three-part dance.
#The old way
import SwiftUI
private struct AccentThemeKey: EnvironmentKey {
static let defaultValue: Color = .blue
}
extension EnvironmentValues {
var accentTheme: Color {
get { self[AccentThemeKey.self] }
set { self[AccentThemeKey.self] = newValue }
}
}
#The new way
import SwiftUI
extension EnvironmentValues {
@Entry var accentTheme: Color = .blue
}
struct ThemedBadge: View {
@Environment(\.accentTheme) private var accentTheme
var body: some View {
Text("New")
.padding(6)
.background(accentTheme, in: Capsule())
}
}
struct ContentView: View {
var body: some View {
ThemedBadge()
.environment(\.accentTheme, .orange)
}
}
#Why it matters
- One line replaces the key struct plus the computed get/set property, for every custom value
- The same macro works for FocusedValues and Transaction keys, not only EnvironmentValues
- The default value comes straight from the property's own initializer, no separate defaultValue declaration to keep in sync
- Fewer moving parts means fewer chances to typo a key name and silently read the wrong default elsewhere
#Gotcha
@Entry requires the default value written right there in the declaration — @Entry var accentTheme: Color = .blue — it can't pull a default from a separate initializer or from logic elsewhere in the file. And it's iOS 18 / macOS 15 and later only: if your app still supports iOS 17, any environment value that needs to work there still needs the old EnvironmentKey boilerplate, since you can't conditionally compile a stored property's declaration style based on OS version at the call site.