In my web view, I'm using CSS variables to change various colors at runtime depending on whether macOS 10.14's dark mode is enabled. That much is working fine. The tricky part is updating the colors when the system appearance changes.
I'm detecting the change by observing the effectiveAppearance
property on the window. That notification comes through as expected, but when I go to update the colors, NSColor
still gives me the dark mode colors (or whichever mode the app started up in). For example, NSColor.textColor
is is still white instead of black when I'm responding to a switch from dark mode to light. The same seems to happen with my own color assets.
Is there a different way or time that I should get these colors? Or could this be an OS bug?
Edit:
I also tried creating a subclass of WebView
and updating my colors in drawRect()
if the name of the web view's effective appearance changes. The first time, I get all light colors, even when the app starts up in dark mode. After that, when I switch from light mode to dark, I get the dark versions of system colors and light versions of asset catalog colors.
Outside the debugger, switching to dark mode works, but the initial load always gets light colors.
Changing the system appearance doesn't change the current appearance, which you can query and set and is independent from the system appearance. But the appearance actually depends on the "owning" view as within the same view hierarchy, several appearances may occur thanks to vibrancy and also manually setting the appearance
property on a view.
Cocoa already updates the current appearance in a few situations, like in drawRect:
, updateLayer
, layout
and updateConstraints
. Everywhere else, you should do it like this:
NSAppearance * saved = [NSAppearance currentAppearance];
[NSAppearance setCurrentAppearance:someView.effectiveAppearance];
// Do your appearance-dependent work, like querying the CGColor from
// a dynamic NSColor or getting its RGB values.
[NSAppearance setCurrentAppearance:saved];
And a Swifty version of the solution proposed by DarkDust:
extension NSAppearance {
static func withAppAppearance<T>(_ closure: () throws -> T) rethrows -> T {
let previousAppearance = NSAppearance.current
NSAppearance.current = NSApp.effectiveAppearance
defer {
NSAppearance.current = previousAppearance
}
return try closure()
}
}
that you can use with
NSAppearance.withAppAppearance {
let bgColor = NSColor.windowBackgroundColor
// ...
}
Note that I'm taking appearance from NSApp but it could be from a NSWindow or NSView.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With