How can dark mode be detected on macOS 10.14?

左心房为你撑大大i 提交于 2019-11-30 02:22:56

Since the actual appearance object you usually get via effectiveAppearance is a composite appearance, asking for its name directly probably isn't a reliable solution.

Asking for the currentAppearance usually isn't a good idea, either, as a view may be explicitly set to light mode or you want to know whether a view is light or dark outside of a drawRect: where you might get incorrect results after a mode switch.

The solution I came up with looks like this:

BOOL appearanceIsDark(NSAppearance * appearance)
{
    if (@available(macOS 10.14, *)) {
        NSAppearanceName basicAppearance = [appearance bestMatchFromAppearancesWithNames:@[
            NSAppearanceNameAqua,
            NSAppearanceNameDarkAqua
        ]];
        return [basicAppearance isEqualToString:NSAppearanceNameDarkAqua];
    } else {
        return NO;
    }
}

You would use it like appearanceIsDark(someView.effectiveAppearance) since the appearance of a specific view may be different than that of another view if you explicitly set someView.appearance.

You could also create a category on NSAppearance and add a - (BOOL)isDark method to get someView.effectiveAppearance.isDark (better chose a name that is unlikely to be used by Apple in the future, e.g. by adding a vendor prefix).

I have used the current appearance checking if the system is 10.14

+ (BOOL)isDarkMode {
    NSAppearance *appearance = NSAppearance.currentAppearance;
    if (@available(*, macOS 10.14)) {
        return appearance.name == NSAppearanceNameDarkAqua;
    }

    return NO;
}

And to detect the change of mode in a view the methods are:

- (void)updateLayer;
- (void)drawRect:(NSRect)dirtyRect;
- (void)layout;
- (void)updateConstraints;

And to detect the change of mode in a view controller the methods are:

- (void)updateViewConstraints;
- (void)viewWillLayout;
- (void)viewDidLayout;

Using notification:

// Monitor menu/dock theme changes...
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];

-(void)themeChanged:(NSNotification *) notification {
    NSLog (@"%@", notification);
}

For more information Dark Mode Documentation

Swift 4

func isDarkMode(view: NSView?) -> Bool {
    if #available(OSX 10.14, *) {
        if let appearance = view?.effectiveAppearance ?? NSAppearance.current {
            return (appearance.name == .darkAqua)
        }
    }
    return false
}

UPDATE:

func isDarkMode(view: NSView) -> Bool {
    if #available(OSX 10.14, *) {
        return view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
    }
    return false
}

To know if the app appearance is Dark, use next code:

+ (BOOL)isDarkMode {
    NSString *interfaceStyle = [NSUserDefaults.standardUserDefaults valueForKey:@"AppleInterfaceStyle"];
    return [interfaceStyle isEqualToString:@"Dark"];
}

For me neither of these answers worked, if I wanted a global state, not per view, and I didn't have access to the view, and I wanted to be notified for updates.

The solution was to ask for NSApp.effectiveAppearance in the main thread, or at least after the current callback method has returned to the system.

So, first I have to register, following the directions of Saúl Moreno Abril, with a code like

[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];

then on the callback method write something like

-(void)themeChanged:(NSNotification *) notification {
    [self performSelectorOnMainThread:@selector(themeChangedOnMainThread) withObject:nil waitUntilDone:false];
}

and then the actual code:

- (void) themeChangedOnMainThread {
    NSAppearance* appearance = NSApp.effectiveAppearance;
    NSString* name = appearance.name;
    BOOL dark = [appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] == NSAppearanceNameDarkAqua;
}

Also the answer from Borzh helped, but is seemed more fragile than the others.

There are actually 8 possible appearances for a view, and 4 of them are for ordinary use. That is,

  1. NSAppearanceNameAqua the Light Mode,
  2. NSAppearanceNameDarkAqua the Dark Mode,
  3. NSAppearanceNameAccessibilityHighContrastAqua Light Mode with increased contrast (set from Accessibility),
  4. NSAppearanceNameAccessibilityHighContrastDarkAqua Dark Mode with increased contrast.

A direct comparison

appearance.name == NSAppearanceNameDarkAqua;

may fail to detect the dark mode if it is with increased contrast. So, always use bestMatchFromAppearancesWithNames instead.

And it is even better to take account of the high-contrast appearances for better accessibility.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!