ARTICLE AD BOX
I use a custom AppTheme type and inject it via a custom EnvironmentKey. Views can access the theme using @Environment(\.appTheme) or override it explicitly.
While this work fine in most cases it somhow causes mysterios crashed deep inside SwiftUI internal code. The crash does not happen inside my code and Xcode does not show a meaningful stack trace.
The crash only happens when using the environment value inside the modifier (see demo code below). Using @Environment(\.appTheme) directly inside views works perfectly.
The theme does not change dynamically and is a plain struct. I only want globally accessible style definitions.
So the question is: Is there anything fundamentally wrong with using a custom theme via @Environment inside a ViewModifier?
I cannot tell whether:
my general theme architecture is flawed, I’m accidentally triggering some SwiftUI limitation regarding Environment inside modifiers, or this is a SwiftUI bug.I’m not trying to implement dynamic theme switching or anything complex — I only want a central place for styles that I can access across the app without manually passing the theme down everywhere.
Before trying to redesign everything, I want to know whether this pattern is supposed to work, or whether SwiftUI has a known limitation here.
Any insights appreciated.
Code:
// Abstract AppTheme build on tokens struct AppThemeTextTokens { var primary: Color var font: Font } struct AppTheme { var text: AppThemeTextTokens //... static let `default` = AppTheme(...) } // Concrete, app specifix app theme extension AppTheme { static let theme = AppTheme( text: .init(primary: .primary, font: .largeTitle) ) //... } // App Theme EnvironmentKey private struct AppThemeKey: EnvironmentKey { static var defaultValue: AppTheme = AppTheme.default } extension EnvironmentValues { var appTheme: AppTheme { get { self[AppThemeKey.self] } set { self[AppThemeKey.self] = newValue } } } // Modifier to apply theme to Text views struct AppThemeTextModifier: ViewModifier { let theme: AppTheme? @Environment(\.appTheme) private var envTheme // <-- This somehow causes the crash func body(content: Content) -> some View { let theme = theme ?? envTheme // <-- Crash // let theme = theme ?? PTAppTheme.theme // <-- No Crash return content .font(theme.text.font) .foregroundColor(theme.text.primary) } } extension View { func textAppTheme(_ theme: AppTheme? = nil) -> some View { modifier(AppThemeTextModifier(theme: theme)) } } // Inject theme to @Environment struct RootView: View { let theme = PTAppTheme.theme var body: some View { SomeSubViews() .environment(\.appTheme, theme) } } // Theme usage via @Environment struct DeepDownSubview: View { @Environment(\.appTheme) private var theme var body: some View { Text("Hello World") .textAppTheme() } }