3
5

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 1 year has passed since last update.

キーボードの押下状態を取得する(macOS/SwiftUI)

Last updated at Posted at 2023-03-04

概要

  • macOS/SwiftUIでキーボードの状態を取得する実装です。
  • 今回はShiftキーや押されているキーの取得、またShift+aの押下状態を検知します。

image

参考

実装

  • keyDown(with:)などNSResponderのイベントをカスタムのNSViewで処理します。
    • イベントはSwiftUIのクラス側で設定できるように渡しています。
    • またNSResponderにあるように、キーボードのイベント以外にもマウスのイベントなども同様に実装することができそうです。
  • カスタムしたNSViewNSViewRepresentableを使って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()
    }
}
3
5
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
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?