ixany
ixany

Reputation: 6080

How to detect switch between macOS default & dark mode using Swift 3

I want to change my status bar app icon when the user switches from default to dark mode and vice versa (using Swift 3). Here’s what i have so far:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    DistributedNotificationCenter.default().addObserver(self, selector: #selector(darkModeChanged(sender:)), name: "AppleInterfaceThemeChangedNotification", object: nil)
}

...

func darkModeChanged(sender: NSNotification) {
    print("mode changed")
}

Unfortunately, it’s not working. What am I doing wrong?

Upvotes: 13

Views: 7331

Answers (4)

FPP
FPP

Reputation: 348

So, my little additions as well:

enum InterfaceStyle: String {
    case Light
    case Dark
    case Unspecified
}

extension Notification.Name {
    static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}

extension NSViewController {
    var interfaceStyle: InterfaceStyle {
        let type = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") ?? "Unspecified"
        return InterfaceStyle(rawValue: type) ?? InterfaceStyle.Unspecified
    }
}

and somewhere in a NSViewController:

        DistributedNotificationCenter.default.addObserver(forName: .AppleInterfaceThemeChangedNotification,
                                                          object: nil, queue: OperationQueue.main) {
            [weak weakSelf = self] (notification) in                // add an observer for a change in interface style
            weakSelf?.setAppearance(toStyle: weakSelf!.interfaceStyle)
        }

where setAppearance reacts on the change of style.

Upvotes: 1

Robin Stewart
Robin Stewart

Reputation: 3913

If you simply need to update icon images for dark mode, you can do this without notifications by creating a dynamic image that updates automatically.

From Apple's documentation:

To create an image that draws its content dynamically, use the init(size:flipped:drawingHandler:) method to initialize your image with a custom drawing handler block. AppKit calls your handler block whenever the system appearance changes, giving you a chance to redraw the image using the new appearance.

Upvotes: 0

backslash-f
backslash-f

Reputation: 8193

Swift 5, Xcode 10.2.1, macOS 10.14.4

Great stuff. My two cents around @Jeffrey's answer:

extension Notification.Name {
    static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}

So one could (instead of rawValue):

func listenToInterfaceChangesNotification() {
    DistributedNotificationCenter.default.addObserver(
        self,
        selector: #selector(interfaceModeChanged),
        name: .AppleInterfaceThemeChangedNotification,
        object: nil
    )
}

Remember the @objc attribute:

@objc func interfaceModeChanged() {
    // Do stuff.
}

Upvotes: 2

Jeffrey Morgan
Jeffrey Morgan

Reputation: 566

I'm using this Swift 3 syntax successfully:

DistributedNotificationCenter.default.addObserver(self, selector: #selector(interfaceModeChanged(sender:)), name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil)

func interfaceModeChanged(sender: NSNotification) {
  ...
}

Upvotes: 18

Related Questions