How can dark mode be detected on macOS 10.14?

前端 未结 6 1273
天涯浪人
天涯浪人 2020-12-13 20:51

In macOS 10.14 users can choose to adopt a system-wide light or dark appearance and I need to adjust some colours manually depend of the current mode.

相关标签:
6条回答
  • 2020-12-13 21:18

    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

    0 讨论(0)
  • 2020-12-13 21:30

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

    + (BOOL)isDarkMode {
        NSString *interfaceStyle = [NSUserDefaults.standardUserDefaults valueForKey:@"AppleInterfaceStyle"];
        return [interfaceStyle isEqualToString:@"Dark"];
    }
    
    0 讨论(0)
  • 2020-12-13 21:36

    Swift 4

    func isDarkMode(view: NSView) -> Bool {
        if #available(OSX 10.14, *) {
            return view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
        }
        return false
    }
    
    0 讨论(0)
  • 2020-12-13 21:36

    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.

    0 讨论(0)
  • 2020-12-13 21:39

    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).

    0 讨论(0)
  • 2020-12-13 21:41

    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.

    0 讨论(0)
提交回复
热议问题