1

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()
    }
}
5
  • Views shown as a modal overlay (such as .sheet or .fullScreenCover, maybe also tips, alerts or popovers) do not inherit the environment of the calling view, environment values have to be passed to these views explicitly. Could this be a possible cause of the problem? Commented yesterday
  • Cannot reproduce on iOS 26.1 with basic assumptions. Please show a minimal reproducible example. Commented yesterday
  • @Sweeper It only crashes in my app an I was not able to reproduce it in a demo so far. As I tried to describe, the question is, whethere there is something fundamentally wrong with my setup? @BenzyNeez I am not sure how this could cause the problem. When @Environment(\.appTheme) is not set, e.g. in a .sheet it should simply default to AppTheme.default and not crash. Commented yesterday
  • No, your setup looks fine to me. Commented yesterday
  • The point of default value is to avoid the need for let theme = theme ?? envTheme Commented yesterday

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.