ひ to り go と

NSAppearance.current の役割

MacDev

なぜか天気予報の全国図で一番気温が高い日が多く 39℃ が当然のように何日も続いていた名古屋にもやっと秋が来た1。秋といえば Apple プラットフォーム開発者が忙しい季節。例によって iOS 12 の話は置いておくとして、ここで取り上げる話題はもちろん macOS Mojave についてだ。

Mojave といえばやはり目玉はダークモード2。WWDC 2018 の関連セッションビデオを見ると NSColor に追加されたシステムカラーの説明に重きが置かれている。

ユーザがいつでもモードを変更できるためこれらのカラーはそれに応じてダイナミックに変化するようになっていて、例えば NSColor.controlBackgroundColor はダークモードで描画すると暗いグレー、そうでなければホワイトとして描画される。

でもどうやって判別しているのか。システム全体が非ダークモード(ライトモード?)であってもダークなウインドウを混ぜることができるし、ライトなウインドウの中に一部だけダークなビューを混ぜることもできてしまう。そのためシステムの設定を取得しても意味がない。

そこで使用されているのが NSAppearance にある current というクラスプロパティ。NSView は draw(_:) とか updateLayer() が呼び出される直前にこれをセットしているため NSColor のシステムカラーは描画相手のことを知らないのに自身の値を変化させることができているようだ。

実験

このプロパティは自力でセットすることも可能なようなので実験してみよう。

import Cocoa

let dynamicColor = NSColor.controlBackgroundColor
let appearanceNames: [NSAppearance.Name] = [.aqua, .vibrantDark]

appearanceNames.forEach {   
    NSAppearance.current = NSAppearance(named: $0)
    if let rgbColor = dynamicColor.usingColorSpace(.deviceRGB) {
        print("\($0.rawValue): (r: \(rgbColor.redComponent), g: \(rgbColor.greenComponent), b: \(rgbColor.blueComponent))")
    }
}
Codes

NSAppearance.current の内容に応じてカラーの RGB 値が異なることがわかる。

NSAppearanceNameAqua: (r: 1.0, g: 1.0, b: 1.0)
NSAppearanceNameVibrantDark: (r: 0.0, g: 0.0, b: 0.0)
Output

アプリケーション開発者は通常はダイナミックなカラーを使用するだけでよく、NSView 自体も effectiveAppearance というプロパティを持っているため NSAppearance.current を触る場面はほとんど存在しないように見えるけど、「drawingHandler で描画する NSImage」みたいに相手先が誰であるか知らないオブジェクトで描画時点のダークモードの有効/無効に応じて独自の変化をさせたい場合には使えそうなプロパティだ。

また、通常の描画でないタイミングでイメージを生成してビットマップとしてキャッシュをしておきたい場合でもこれをセットすれば任意のアピアランスに応じたカラーを使用できそう。

お片づけ

NSGraphicsContext などと同様、自力でセットする場合は念のため最後にもとの値に戻るようにするのをお忘れなく。こういうお片づけ処理は Swift の defer {} を使うと最初にまとめて書けてすっきりだ。

let oldAppearance = NSAppearance.current
defer { NSAppearance.current = oldAppearance }

NSAppearance.current = ...

  1. あのイベントで一人だけ涼しい服装をしていたのは自分が季節感のない人間だからではないと強調しておく。 ↩︎

  2. 個人的にはアクセントカラーが選べるようになったことの方がうれしい。 ↩︎

Share

リンクも共有もお気軽に。記事を書くモチベーションの向上に役立てます。

© 2005-2019 zumuya