Swift の KeyPath を使った Binding
Cocoa Bindings は Objective-C 時代にできたものだから String
でプロパティのキーパスを指定するけど、 Swift 4 の KeyPath
と Generics を組み合わせればすっきりしたコードで書けるのではないかと思いついたので実験。
protocol KeyPathBasedBinding {}
extension KeyPathBasedBinding
{
/// When the returned object is deinited or invalidated, it will stop binding.
/// - Parameter targetKeyPath: The KeyPath for target property. Target property must be observable through KVO.
func bind<Value, Target>(_ keyPath: ReferenceWritableKeyPath<Self, Value>, to targetKeyPath: KeyPath<Target, Value>, of target: Target) -> NSKeyValueObservation where Self: AnyObject, Target: _KeyValueCodingAndObserving
{
return target.observe(targetKeyPath, options: [.initial, .new], changeHandler: { [weak self] (sender, change) in
self?[keyPath: keyPath] = target[keyPath: targetKeyPath]
})
}
/// When the returned object is deinited or invalidated, it will stop binding.
/// - Parameter targetKeyPath: The KeyPath for target property. Target property must be observable through KVO.
func bind<Value, Target>(_ keyPath: ReferenceWritableKeyPath<Self, Value>, to targetKeyPath: KeyPath<Target, Optional<Value>>, of target: Target, nullValue: Value) -> NSKeyValueObservation where Self: AnyObject, Target: _KeyValueCodingAndObserving
{
return target.observe(targetKeyPath, options: [.initial, .new], changeHandler: { [weak self] (sender, change) in
self?[keyPath: keyPath] = (target[keyPath: targetKeyPath] ?? nullValue)
})
}
}
extension NSObject: KeyPathBasedBinding {}
Cocoa Bindings だと bind(_,to:,withKeyPath:,options)
の第一引数には自身のプロパティのキーもしくは特別に用意された NSBindingName
の定数を使用することができるけど、今回再現しているのは前者だけだ。
NSVisualEffectView の角を丸くする 2 つの方法
画面の端にぴったりくっつけることが多い iOS と違い、macOS だと余白を設けてビューを配置することが多い。そうすると角を丸くしたくなる。木工家具でやすりがけや面取りをするような基本的な話だ。
AppKit の標準コントロールのほとんどはそういう仕上げがしてあるから意識する必要はないのだけど、先日 NSVisualEffectView
の角を丸くしたい状況があったのでやり方を考えてみた。
NSAppearance.current の役割
なぜか天気予報の全国図で一番気温が高い日が多く 39℃ が当然のように何日も続いていた名古屋にもやっと秋が来た1。秋といえば Apple プラットフォーム開発者が忙しい季節。例によって iOS 12 の話は置いておくとして、ここで取り上げる話題はもちろん macOS Mojave についてだ。
Mojave といえばやはり目玉はダークモード2。WWDC 2018 の関連セッションビデオを見ると NSColor
に追加されたシステムカラーの説明に重きが置かれている。
ユーザがいつでもモードを変更できるためこれらのカラーはそれに応じてダイナミックに変化するようになっていて、例えば NSColor.controlBackgroundColor
はダークモードで描画すると暗いグレー、そうでなければホワイトとして描画される。
でもどうやって判別しているのか。システム全体が非ダークモード(ライトモード?)であってもダークなウインドウを混ぜることができるし、ライトなウインドウの中に一部だけダークなビューを混ぜることもできてしまう。そのためシステムの設定を取得しても意味がない。
そこで使用されているのが NSAppearance
にある current
というクラスプロパティ。NSView
は draw(_:)
とか updateLayer()
が呼び出される直前にこれをセットしているため NSColor
のシステムカラーは描画相手のことを知らないのに自身の値を変化させることができているようだ。
“Mac 開発話が聴ける Podcast エピソードまとめ”を公開
iOSDC に初参加した勢いで勝手に変なまとめを作ってしまった。
iOS の世界とは異なり、macOS のソフトウェアを開発する人は少ない。さらに Cocoa ネイティブ、国内に限定したら 100 人いるのか不安になるレベル。
そんな開発者がごくまれに Podcast や YouTube に登場して話をしていることがある。どれも滅多に聞けない貴重な内容であり、本当に必要としている人に届かないのはもったいない。
そこで、このページではそんなエピソードを発見次第登録していく。
音声メディアじゃないと発信されないような内容も多いので、少しでも興味がある人は聞かないともったいない!
RawRepresentable のままで Dictionary にアクセス
enum MyKey: String
{
case identifier
case colorCode
}
func process(_ dictionary: [String: Any])
{
let identifier = dictionary[MyKey.identifier.rawValue] as? String
let colorCode = dictionary[MyKey.colorCode.rawValue] as? Int
...
}
この .rawValue
を何度も書きたくなかったので extension を書いたという話。シンプルなコードなので誰が書いてもほぼ同じになりそう:
extension Dictionary
{
subscript<WrappedKey: RawRepresentable>(_ key: WrappedKey) -> Value? where WrappedKey.RawValue == Key
{
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}
let identifier = dictionary[MyKey.identifier] as? String
let colorCode = dictionary[MyKey.colorCode] as? Int
もちろん最初から [MyKey: Any]
にした方がきれいな状況であればその方がいいと思う。すっきりした勢いだけで書いた内容のない記事でした。
callback を登録解除できない IOKit のバグ
IOKit で HID デバイスからデータを受け取るには IOHIDDeviceRegisterInputReportCallback()
で callback を登録する。逆に callback の登録を解除したいときは専用の関数があるわけではなく Apple の資料によると
Note: To unregister pass NULL for the callback.
登録するときと同じ関数を、(callback: NULL)
で呼べばいいらしい。
...と知ってからずっと試しているけど、どんな書き方をしても解除したはずの古い context で callback が呼ばれてしまい、アプリケーションがクラッシュ。
どうしようもないので IOKit のソースコードを読んでみることに。