How can dark mode be detected on macOS 10.14?
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).
Swift 4
func isDarkMode(view: NSView) -> Bool {
if #available(OSX 10.14, *) {
return view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
}
return false
}
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
There are actually 8 possible appearances for a view, and 4 of them are for ordinary use. That is,
NSAppearanceNameAqua
the Light Mode,NSAppearanceNameDarkAqua
the Dark Mode,NSAppearanceNameAccessibilityHighContrastAqua
Light Mode with increased contrast (set from Accessibility),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.