Updating for dark mode: NSColor ignores appearance changes?

The currentAppearance property used in previous answers is now deprecated. The alternative as of macOS 11 Big Sur is to use the performAsCurrentDrawingAppearance: instance method.


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.