SwiftUI MenuBarExtra icon and dark mode

203 views Asked by At

I want my icon to be light/dark according to current system color theme, but I never seem to get the dark color image to show, its always the light image that shows up

MenuBarExtra("", image: "my image name") {
    Text("some text")
}

also tried using label but the "MenuLogoView" does not get any callback while switching color mode:

MenuBarExtra {
    MenuView(model: menuModel)
} label: {
    MenuLogoView()
}

struct MenuLogoView: View {
    @Environment(\.colorScheme) var colorScheme
...

but "colorScheme" never seems to change

if I use the same image resource in other places of my code it works well for both light/dark color theme modes

1

There are 1 answers

4
Sweeper On BEST ANSWER

It appears that, indeed, MenuBarExtras don't get their colorScheme environment set to the correct value. I'm not sure if this is intentional.

One rather nasty trick is to get the "real" color scheme from one of your Views in a window.

@State var realColorScheme: ColorScheme = .light
var body: some Scene {
    WindowGroup {
        ContentView(colorBinding: $realColorScheme)
    }
    MenuBarExtra {
        MenuView(model: menuModel)
    } label: {
        Image("my_image")
            .environment(\.colorScheme, realColorScheme)
    }
}

where ContentView is:

struct ContentView: View {
    
    @Environment(\.colorScheme) var color
    
    @Binding var colorBinding: ColorScheme
    
    var body: some View {
        // content here...
            .onAppear {
                colorBinding = color
            }
            .onChange(of: color) {
                colorBinding = $0
            }
    }
}

An alternative way is to detect dark mode by reading UserDefaults with @AppStorage, described here. This would work even if your app is only a MenuBarExtra. The user defaults updates a bit more slowly than the @Environment approach above though, from my experience.

@State var realColorScheme: ColorScheme = .light
@AppStorage("AppleInterfaceStyle") var colorName: String?

var body: some Scene {
    MenuBarExtra {
        MenuView(model: menuModel)
    } label: {
        Image("my_image")
            .onAppear {
                realColorScheme = colorSchemeFromUserDefaultsValue(colorName)
            }
            .onChange(of: colorName) {
                realColorScheme = colorSchemeFromUserDefaultsValue($0)
            }
            .environment(\.colorScheme, realColorScheme)
    }
}

func colorSchemeFromUserDefaultsValue(_ value: String?) -> ColorScheme {
    if value == "Dark" {
        return .dark
    } else {
        return .light
    }
}