🐰

NSViewベースのカスタムビューをキーボードナビゲーションとフォーカスリングに対応させる

2024/02/24に公開

macOS AppKitのコントロールはキーボードナビゲーションに対応しており、フォーカスが当たると周囲にフォーカスリングが描画される仕組みになっています。キーボードナビゲーションはTabで次のキービューに移動、Shift Tabで前のキービューに移動させることができ、SpaceやReturnで実行することも可能です。AppKit標準のUIコントロールであれば特段何もする必要はないのですが、NSViewベースで独自にカスタムコントロール(またはビュー)を設計する場合、キーボードナビゲーションへの対応とフォーカスリングの描画範囲を適切に制御する必要があります。この対応を行うことで、独自設計のUI要素をよりアクセシブルに仕立て上げることが可能となります。


フォーカスリング

フォーカスリングを表示してキーボードナビゲーションを使えるようにするには、システム設定でキーボードナビゲーションを有効にする必要があります。

関連:
https://zenn.dev/usagimaru/articles/3244c884488ea7

フォーカスリング対応

canBecomeKeyView

canBecomeKeyViewはNSViewサブクラスで実装し、そのビューがキービューとして振る舞えるかどうかを決めるブーリアン値を返すようにします。trueを返せばキービュー、すなわちキーボードナビゲーションでのフォーカス対象に組み込まれます(Key-view loopとも呼ばれます)。ビュー自体のステートに合わせて適宜true/falseを制御することで、より柔軟な対応も可能です。

focusRingMaskBounds

focusRingMaskBoundsはNSViewサブクラスで実装し、フォーカスリングを描画するマスク矩形(ビューの内部座標系が基準)を返すようにします。このプロパティを提供すると、システムはフォーカスリングを実際に描画する前におおよその位置と大きさを決定することができます。

カスタムビューでフォーカスリングの描画がうまくいかない場合にはこのプロパティの挙動を調査してみてください。

drawFocusRingMask()

drawFocusRingMask()はNSViewサブクラスで実装し、フォーカスリングのマスク矩形を描画する処理を書き込みます。ビューの内部座標系を基準に描画します。マスクする範囲は完全に不透明な色にする必要があります。

例えば任意の角R矩形でビュー全体をマスキングする場合は次のようにします。
(ビューに一致する位置と大きさで、半径5ptの角丸長方形のフォーカスリングにします)

override var canBecomeKeyView: Bool {
	true
}

override var focusRingMaskBounds: NSRect {
	bounds
}

override func drawFocusRingMask() {
	NSBezierPath(roundedRect: bounds, xRadius: 5, yRadius: 5).fill()
}

操作

NSControl.performClick(_:)

performClick(_:)はNSControlベースの実装でクリックアクションをシミュレートするためのメソッドです。大抵はクリック操作をプログラムで再現するために使用しますが、Spaceキーによる代替クリック操作によってこのメソッドが呼ばれることがあるので、クリック操作で実行したい処理をこのメソッド経由でも呼び出せるようにしておきましょう。

Discussion