6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

macOSアプリのダークモード対応

Last updated at Posted at 2019-10-20

はじめに

macOSでは10.14(2018年リリース)からダークモードが追加されています。

macOS 10.14以降、アプリで独自定義のカラーを使う場合、ライトモード用とダークモード用のカラーを用意しておく必要があります。ライト/ダーク用のカラーはカラーアセットを利用して用意できます。

スクリーンショット 2019-10-20 19.50.47.png

カラーアセットから以下のコードで外観モードにあわせたカラーオブジェクトを生成できます。

let color = NSColor(named: NSColor.Name("MyColor"))

しかし、この方法ではモードを切り替え時、起動中のアプリでは自動的にカラーが変更されません。macOS 10.15ではNSColorにinit(name:dynamicProvider:)イニシャライザが追加されており、ドキュメントに説明は無いですが、察するにこれを使えば動的に変更できるものと思います。では、macOS 10.14ではどうするか?

macOS 10.14でダークモードに対応する

macOS 10.14では、独自定義のカラーをViewに設定していた場合、モードの変更を検知し、カラーを設定し直すコードを書く必要があります。

ダークモードか判定する

ダークモードか判定するisDarkModeコンピューテッドプロパティをNSApplicationに生やします。

extension NSApplication {
    public var isDarkMode: Bool {
        if #available(OSX 10.14, *) {
            let name = effectiveAppearance.name
            return name == .darkAqua
        }
        else {
            return false
        }
    }
}

外観モードにあわせてカラーオブジェクトを返す

ライト/ダークモード用のカラーオブジェクトを返す適当なメソッドを実装します。

struct CustomColors {
    static var background: NSColor {
        if NSApplication.shared.isDarkMode {
            return NSColor(red: 34.0 / 255.0, green: 34.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
        }
        return .white    // for Light Mode
    }
}

外観モードの変更を検知する

外観モードが変更されると、NSViewのviewDidChangeEffectiveAppearanceメソッドが呼ばれます。このメソッドをオーバライドし、カラーオブジェクトを再設定するようにします。

class CustomView: NSView {

    @available(OSX 10.14, *)
    override func viewDidChangeEffectiveAppearance() {
        layer?.backgroundColor = CustomColors.background.cgColor
    }
}

これで動的にカラーを変更することができます。ただ、このやり方は各ビューで継承クラスを作らないといけないため、使い勝手が悪いときがあります。

そこで僕は以下のようなクラスを作って監視するようにしています。

class AppearanceMonitor: NSView {

    public static let appearanceChangedNotification = NSNotification.Name("AppearanceChangedNotification")
    public static let shared                        = AppearanceMonitor(frame: .zero)

    public var isMonitoring: Bool {
        superview != nil
    }

    override func viewDidMoveToSuperview() {
        guard let _ = superview else {
            print("[\(self.className)] Monitoring stopped.")
            return
        }
        print("[\(self.className)] Monitoring started.")
    }

    @available(OSX 10.14, *)
    override func viewDidChangeEffectiveAppearance() {
        NotificationCenter.default.post(name: AppearanceMonitor.appearanceChangedNotification, object: nil, userInfo: nil)
    }

    public func start(with viewController: NSViewController) {
        if !isMonitoring {
            viewController.view.addSubview(self)
        }
    }

    public func stop() {
        if isMonitoring {
            removeFromSuperview()
        }
    }
}
class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 監視用のビューを貼る(frame=.zeroなので見た目には影響がない)
        AppearanceMonitor.shared.start(with: self)
    }

    override func viewWillAppear() {
        super.viewWillAppear()

        NotificationCenter.default.addObserver(self,
                                               selector: #selector(appearanceChanged),
                                               name: AppearanceMonitor.appearanceChangedNotification,
                                               object: nil)
    }

    override func viewWillDisappear() {
        super.viewWillDisappear()

        NotificationCenter.default.removeObserver(self,
                                                  name: AppearanceMonitor.appearanceChangedNotification,
                                                  object: nil)
    }

    @objc func appearanceChanged() {
        // ライト/ダーク・モード切り替え時に呼ばれるのでUIを更新する
    }
}

なお、他にAppleInterfaceThemeChangedNotificationを監視する方法もありますが、公式ドキュメントに記載がない(?)ため、積極的に使っていいものなのか悩ましいです。

プログラムでライト/ダーク・モードを切り替える

最後に、プログラムでライト/ダーク・モードを切り替える方法を紹介します。

// ダークモードにする
NSApp.appearance = NSAppearance(named: .darkAqua)
// ライドモードにする
NSApp.appearance = NSAppearance(named: .aqua)
// プログラムでの設定を解除する -> Macの設定に従う
NSApp.appearance = nil

参考

Supporting Dark Mode in Your Interface
https://developer.apple.com/documentation/xcode/supporting_dark_mode_in_your_interface

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?