概要
- macOS/SwiftUIでキーボードの状態を取得する実装です。
- 今回はShiftキーや押されているキーの取得、また
Shift+a
の押下状態を検知します。
参考
実装
-
keyDown(with:)など
NSResponder
のイベントをカスタムのNSView
で処理します。- イベントはSwiftUIのクラス側で設定できるように渡しています。
- またNSResponderにあるように、キーボードのイベント以外にもマウスのイベントなども同様に実装することができそうです。
- カスタムした
NSView
はNSViewRepresentable
を使ってSwiftUI側から使用します。 - flagsChanged(with:)は修飾キー(modifier key)の押下状態が変更された場合に呼ばれます。
-
keyDown(with:)やkeyUp(with:)はそれぞれキーボードが押される/押されなくなる、場合に呼ばれます。
- また
shift
キーを単独で押した場合keyDown
は呼ばれないため、flagsChanged
側で処理する必要があります。
- また
struct KeyEventHandling: NSViewRepresentable {
// 親ビューから渡される
var flagsChangedHandler: ((NSEvent) -> ())?
var keyDownHandler: ((NSEvent) -> ())?
var keyUpHandler: ((NSEvent) -> ())?
class KeyView: NSView {
var flagsChangedHandler: ((NSEvent) -> ())?
var keyDownHandler: ((NSEvent) -> ())?
var keyUpHandler: ((NSEvent) -> ())?
override func awakeFromNib() {
super.awakeFromNib()
}
override var acceptsFirstResponder: Bool {
true
}
override func flagsChanged(with event: NSEvent) {
super.flagsChanged(with: event)
flagsChangedHandler?(event)
}
override func keyDown(with event: NSEvent) {
super.keyDown(with: event)
keyDownHandler?(event)
}
override func keyUp(with event: NSEvent) {
super.keyUp(with: event)
keyUpHandler?(event)
}
}
func makeNSView(context: Context) -> NSView {
let view = KeyView()
view.flagsChangedHandler = flagsChangedHandler
view.keyDownHandler = keyDownHandler
view.keyUpHandler = keyUpHandler
DispatchQueue.main.async { // wait till next event cycle
view.window?.makeFirstResponder(view)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
}
}
呼び出し側
-
.background
経由で定義したKeyEventHandling
を使用します。
import SwiftUI
struct ContentView: View {
@State private var modifierFlags: NSEvent.ModifierFlags = []
@State private var keyDownCharacter: String? = nil
@State private var shiftAndAKeyDown: Bool = false
var body: some View {
statusView()
.background(KeyEventHandling(
flagsChangedHandler: { event in
modifierFlags = event.modifierFlags
},
keyDownHandler: { event in
keyDownCharacter = event.characters
// shift + a
shiftAndAKeyDown = (event.modifierFlags.contains(.shift) && event.keyCode == 0)
},
keyUpHandler: { event in
// 設定のリセット
keyDownCharacter = nil
shiftAndAKeyDown = false
}))
}
@ViewBuilder
private func statusView() -> some View {
Grid(verticalSpacing: 8) {
GridRow {
Text("Shift:")
.gridColumnAlignment(.trailing)
if modifierFlags.contains(.shift) {
Image(systemName: "checkmark.circle")
.imageScale(.large)
.foregroundColor(.green)
} else {
Image(systemName: "x.circle")
.imageScale(.large)
.foregroundColor(.gray)
}
}
GridRow {
Text("Keydown:")
Text(keyDownCharacter ?? "(nil)")
.frame(minWidth: 40)
}
GridRow {
Text("Shift + a:")
if shiftAndAKeyDown {
Image(systemName: "checkmark.circle")
.imageScale(.large)
.foregroundColor(.green)
} else {
Image(systemName: "x.circle")
.imageScale(.large)
.foregroundColor(.gray)
}
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}