HardwareKeyboardInputView.swift (5886B)
1 import SwiftUI 2 import UIKit 3 4 struct HardwareKeyboardEvent { 5 let keyCode: UIKeyboardHIDUsage 6 let charactersIgnoringModifiers: String 7 let modifierFlags: UIKeyModifierFlags 8 } 9 10 struct HardwareKeyboardInputView: UIViewRepresentable { 11 var onPress: (HardwareKeyboardEvent) -> Bool 12 13 func makeUIView(context: Context) -> KeyCaptureView { 14 let view = KeyCaptureView() 15 view.onPress = onPress 16 return view 17 } 18 19 func updateUIView(_ uiView: KeyCaptureView, context: Context) { 20 uiView.onPress = onPress 21 uiView.ensureFirstResponder() 22 } 23 24 final class KeyCaptureView: UIView { 25 var onPress: ((HardwareKeyboardEvent) -> Bool)? 26 27 override var canBecomeFirstResponder: Bool { true } 28 29 override var keyCommands: [UIKeyCommand]? { 30 let letters = "abcdefghijklmnopqrstuvwxyz".map { 31 UIKeyCommand( 32 input: String($0), 33 modifierFlags: [], 34 action: #selector(handleKeyCommand(_:)) 35 ) 36 } 37 38 let undo = UIKeyCommand(input: "z", modifierFlags: .command, action: #selector(handleKeyCommand(_:))) 39 undo.discoverabilityTitle = "Undo Move" 40 let redo = UIKeyCommand(input: "z", modifierFlags: [.command, .shift], action: #selector(handleKeyCommand(_:))) 41 redo.discoverabilityTitle = "Redo Move" 42 43 return letters + [ 44 undo, 45 redo, 46 UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(handleKeyCommand(_:))), 47 UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(handleKeyCommand(_:))), 48 UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: .command, action: #selector(handleKeyCommand(_:))), 49 UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: .command, action: #selector(handleKeyCommand(_:))), 50 UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(handleKeyCommand(_:))), 51 UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(handleKeyCommand(_:))), 52 UIKeyCommand(input: "\u{8}", modifierFlags: [], action: #selector(handleKeyCommand(_:))), 53 UIKeyCommand(input: "\u{7F}", modifierFlags: [], action: #selector(handleKeyCommand(_:))), 54 UIKeyCommand(input: "\t", modifierFlags: [], action: #selector(handleKeyCommand(_:))), 55 UIKeyCommand(input: "\t", modifierFlags: .shift, action: #selector(handleKeyCommand(_:))), 56 UIKeyCommand(input: " ", modifierFlags: [], action: #selector(handleKeyCommand(_:))), 57 UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(handleKeyCommand(_:))), 58 UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(handleKeyCommand(_:))) 59 ] 60 } 61 62 override func didMoveToWindow() { 63 super.didMoveToWindow() 64 ensureFirstResponder() 65 } 66 67 func ensureFirstResponder() { 68 guard window != nil, !isFirstResponder else { return } 69 Task { @MainActor in 70 self.becomeFirstResponder() 71 } 72 } 73 74 override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) { 75 var unhandled: [UIPress] = [] 76 77 for press in presses { 78 guard let key = press.key else { 79 unhandled.append(press) 80 continue 81 } 82 83 let event = HardwareKeyboardEvent( 84 keyCode: key.keyCode, 85 charactersIgnoringModifiers: key.charactersIgnoringModifiers, 86 modifierFlags: key.modifierFlags 87 ) 88 89 if onPress?(event) != true { 90 unhandled.append(press) 91 } 92 } 93 94 if !unhandled.isEmpty { 95 super.pressesBegan(Set(unhandled), with: event) 96 } 97 } 98 99 @objc private func handleKeyCommand(_ command: UIKeyCommand) { 100 guard let event = HardwareKeyboardEvent( 101 characters: command.input ?? "", 102 modifierFlags: command.modifierFlags 103 ) else { return } 104 _ = onPress?(event) 105 } 106 } 107 } 108 109 private extension HardwareKeyboardEvent { 110 init?(characters: String, modifierFlags: UIKeyModifierFlags) { 111 let keyCode: UIKeyboardHIDUsage 112 113 switch characters { 114 case UIKeyCommand.inputLeftArrow: 115 keyCode = .keyboardLeftArrow 116 case UIKeyCommand.inputRightArrow: 117 keyCode = .keyboardRightArrow 118 case UIKeyCommand.inputUpArrow: 119 keyCode = .keyboardUpArrow 120 case UIKeyCommand.inputDownArrow: 121 keyCode = .keyboardDownArrow 122 case UIKeyCommand.inputEscape: 123 keyCode = .keyboardEscape 124 case "\u{8}": 125 keyCode = .keyboardDeleteOrBackspace 126 case "\u{7F}": 127 keyCode = .keyboardDeleteForward 128 case "\t": 129 keyCode = .keyboardTab 130 case " ": 131 keyCode = .keyboardSpacebar 132 case "\r": 133 keyCode = .keyboardReturnOrEnter 134 case "a"..."z", "A"..."Z": 135 guard let scalar = characters.uppercased().unicodeScalars.first, 136 let code = UIKeyboardHIDUsage(rawValue: Int(scalar.value - 65) + UIKeyboardHIDUsage.keyboardA.rawValue) else { 137 return nil 138 } 139 keyCode = code 140 default: 141 return nil 142 } 143 144 self.init( 145 keyCode: keyCode, 146 charactersIgnoringModifiers: characters, 147 modifierFlags: modifierFlags 148 ) 149 } 150 }