63

My cocoa app has to change its behaviour when run in the new OS X "dark mode".

Is there a way to detect if OS X style is set to this mode?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Augustus1
  • 665
  • 1
  • 5
  • 6
  • 2
    stab in the dark - have you tried listening to `NSScreenColorSpaceDidChangeNotification` or examining `NSScreen` `colorSpace` property. On Mav's at the moment so can't check. – Warren Burton Aug 08 '14 at 15:49

11 Answers11

92

Don't think there's a cocoa way of detecting it yet, however you can use defaults read to check whether or not OSX is in dark mode.

defaults read -g AppleInterfaceStyle

Either returns Dark (dark mode) or returns domain pair does not exist.

EDIT:

As Ken Thomases said you can access .GlobalPreferences via NSUserDefaults, so

NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];

If osxMode is nil then it isn't in dark mode, but if osxMode is @"Dark" then it is in dark mode.

TheAmateurProgrammer
  • 9,252
  • 8
  • 52
  • 71
  • 4
    The `defaults` command is just a wrapper around the `CFPreferences` API, as is `NSUserDefaults`. So, you can use either of those APIs rather than invoking `defaults`. – Ken Thomases Aug 09 '14 at 05:00
  • 1
    It's @"Dark", not @"dark" on my machine. – nschum Aug 12 '14 at 19:27
  • Is there any way of getting notified when the mode is changed? – houbysoft Oct 21 '14 at 18:29
  • 2
    @houbysoft Use [KVO](http://stackoverflow.com/questions/1141388/cocoa-notification-on-nsuserdefaults-value-change) – TheAmateurProgrammer Oct 22 '14 at 06:34
  • @TheAmateurProgrammer thanks that looks like exactly what I want! – houbysoft Oct 22 '14 at 19:22
  • This works for detection, but in OS Sierra the only thing that changes when you programmatically set the mode to dark is the dock. Menu bar and notifications don't change. Any ideas on how to fix/change this? Thanks – xandermonkey Dec 03 '16 at 16:54
  • @Husam, are you sure? It works in 10.14.3 beta 2 which I'm currently running. – Muntashir Akon Jan 09 '19 at 13:35
  • 9
    Doesn't work on Catalina: "The domain/default pair of (kCFPreferencesAnyApplication, AppleInterfaceStyle) does not exist" – bas Oct 29 '19 at 12:27
  • Catalina works perfectly. I just check NSString is DarkMode or no – Genevios Jan 22 '20 at 11:36
  • 7
    @bas "Does not exist" probably means "light mode". – JeremyP Mar 02 '20 at 20:05
  • 6
    I can confirm (on Catalina), if in dark mode, then `defaults read -g AppleInterfaceStyle` returns "Dark", otherwise it returns "The domain/default pair of (kCFPreferencesAnyApplication, AppleInterfaceStyle) does not exist". Quite unintuitive :) – Victor Jul 22 '20 at 08:10
  • @Victor, does it work smoothly with the automatic switching between modes during night time from macOS? – ggrelet Oct 30 '20 at 12:52
  • 1
    @ggrelet it does! I use it in my Vim config to automatically switch between night and dark color schemes when macOS switches to night time. Works like a charm :) – Victor Nov 02 '20 at 11:06
  • It does not work for me: when on Automatic switch, the value of that pref is always `Dark` – ggrelet Mar 20 '21 at 16:56
  • 1
    Doesn't work on 11.3.1 on Apple Silicon: `The domain/default pair of (kCFPreferencesAnyApplication, AppleInterfaceStyle) does not exist` – adib May 23 '21 at 01:48
  • `defaults read -g AppleInterfaceStyle` doesn't work on Big Sur 11.6 (Intel) either: `The domain/default pair of (kCFPreferencesAnyApplication, AppleInterfaceStyle) does not exist` – pyb Sep 28 '21 at 16:56
  • 1
    @pyb Did you read [the comment above from JeremyP](https://stackoverflow.com/questions/25207077/how-to-detect-if-os-x-is-in-dark-mode#comment107021809_25214873)? – Franklin Yu Jul 07 '22 at 06:15
  • @FranklinYu yes, he's right. This is how I solved it in shell: `macos_is_dark() { test "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" == "Dark" }` – pyb Jul 07 '22 at 12:16
40

Swift 2 -> String ("Dark", "Light")

let appearance = NSUserDefaults.standardUserDefaults().stringForKey("AppleInterfaceStyle") ?? "Light"

Swift 3 -> Enum (Dark, Light)

enum InterfaceStyle : String {
   case Dark, Light

   init() {
      let type = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") ?? "Light"
      self = InterfaceStyle(rawValue: type)!
    }
}

let currentStyle = InterfaceStyle()
Andrey
  • 658
  • 7
  • 14
21

You can detect this using NSAppearanceCustomization method effectiveAppearance, by checking for darkAqua.

Swift 4 example:

extension NSView {
    var isDarkMode: Bool {
        if #available(OSX 10.14, *) {
            if effectiveAppearance.name == .darkAqua {
                return true
            }
        }
        return false
    }
}
James Eunson
  • 215
  • 2
  • 5
  • I'm using `[NSAppearance.Name.darkAqua, NSAppearance.Name.vibrantDark].contains(effectiveAppearance.name)` to check both dark appearances – GP89 May 17 '21 at 09:58
15

You can also wrap it in a boolean if you don't feel like dealing with enums and switch statements:

/// True if the application is in dark mode, and false otherwise
var inDarkMode: Bool {
    let mode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle")
    return mode == "Dark"
}

Works on Swift 4.2

J.beenie
  • 861
  • 10
  • 22
13

For working with the new macOS Catalina you need to combine AppleInterfaceStyle with this new value introduced AppleInterfaceStyleSwitchesAutomatically.

Here is some pseudo-code explaining how to:

theme = light //default is light
if macOS_10.15
    if UserDefaults(AppleInterfaceStyleSwitchesAutomatically) == TRUE
        if UserDefaults(AppleInterfaceStyle) == NIL
            theme = dark // is nil, means it's dark and will switch in future to light
        else
            theme = light //means it's light and will switch in future to dark
        endif
    else
        if UserDefaults(AppleInterfaceStyle) == NIL
            theme = light
        else
            theme = dark
        endif
    endif
else if macOS_10.14
    if UserDefaults(AppleInterfaceStyle) == NIL
        theme = light
    else
        theme = dark
    endif
endif

You can check a macOS sample app here: https://github.com/ruiaureliano/macOS-Appearance.

(Disclaimer: I am the author of this sample app.)

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
ruiaureliano
  • 1,552
  • 1
  • 14
  • 13
12

I would check against all dark appearances like so

extension NSView {

    var hasDarkAppearance: Bool {
        if #available(OSX 10.14, *) {
            switch effectiveAppearance.name {
            case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
                return true
            default:
                return false
            }
        } else {
            switch effectiveAppearance.name {
            case .vibrantDark:
                return true
            default:
                return false
            }
        }
    }
}
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
4

This works:

if #available(OSX 10.14, *) {
    inputTextView.textColor = (NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua ? NSColor.white : NSColor.black)
}
Alex
  • 171
  • 2
  • 10
leonardosccd
  • 1,503
  • 11
  • 12
3

This isn't a complete answer to the question because the questioner doesn't say what their use case is. If they want completely different behaviour of their app, the below behaviour doesn't work. However, if they only want to change the colour of some custom view, this is the Apple blessed way.

The thing to do is to stop using absolute colours and start using semantic colours. This means defining a "colour set" for each colour you want to use in the assets catalog. Having defined your colour set, in the inspector, set the device to "Mac" and the appearance to "Any, Light, Dark". You will then get three colour wells, "any" is for legacy operating systems that do not support dark mode, "light" and "dark" should be obvious.

Here is an example:

Defining a colour set that supports dark mode

This defines a colour that will be white in dark mode and black in light mode or on legacy operating systems.

Once you have defined a colour set, you can retrieve the colour in your draw(_ dirtyRect:) as follows:

let strokeColour = NSColor(named: NSColor.Name("gridColour")) ?? NSColor.black

In the above, I default to black if the colour set does not exist to deal with the optional type of NSColor(named:).

JeremyP
  • 84,577
  • 15
  • 123
  • 161
2

2020 | SWIFT 5.1:

WAY 1:

@Environment(\.colorScheme) var scheme

WAY 2:

doesn't update swiftUI in case of theme change. Need to realize additional logic for view upd:

#available(OSX 10.14, *)
static private var isLight: Bool { NSApp.effectiveAppearance.name == NSAppearance.Name.aqua }

#available(OSX 10.14, *)
static private var isDark: Bool { NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua }

Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
  • Just add an observer for the `AppleInterfaceThemeChangedNotification` distributed notification and go from there. – red_menace Feb 17 '20 at 22:13
2

The only safe way to check for dark mode is to use the following:

let viewUsesDarkMode: Bool
if #available(OSX 10.14, *) {
    viewUsesDarkMode = view.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
} else {
    viewUsesDarkMode = false
}

This is the only solution that works in all cases. Whether you have views with mixed appearances, or if you allow your app to use a different appearance than the system default, or if you configure your system to use the high contrast appearances.

Jakob Egger
  • 11,981
  • 4
  • 38
  • 48
  • This could be modified to check for both regular and vibrant. ```if #available(macOS 10.14, *) { if view.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua || view.effectiveAppearance.bestMatch(from: [.vibrantLight, .vibrantDark]) == .vibrantDark { viewUsesDarkMode = true } else { viewUsesDarkMode = false } } else { viewUsesDarkMode = false }``` – SouthernYankee65 Mar 11 '22 at 18:13
  • 1
    @SouthernYankee65 This is unnecessary. `bestMatch` chooses the closest appearance from the provided options. In this case, if `effectiveAppearance` happened to be `.vibrantDark`, then the result would still be `.darkAqua`. – Jakob Egger May 07 '22 at 10:18
1

Take a look at NSAppearance.Name (in Swift speak) - there are variants:

.darkAqua

.accessibilityHighContrastDarkAqua

.accessibilityHighContrastVibrantDark