I want to respond to key presses, such as the esc
key on macOS/OSX, and when using an external keyboard on iPad. How can I do this?
I have thought of using @available
/#available
with SwiftUI's onExitCommand
, which looked very promising, but unfortunately that is only for macOS/OSX. How can I respond to key presses in SwiftUI for more than just macOS/OSX?
.keyboardShortcut(_:modifiers:)
.With thanks to @Asperi to pointing me in the right direction, I have now managed to get this working.
The solution was to use UIKeyCommand
. Here is what I did, but you can adapt it differently depending on your situation.
I have an @EnvironmentObject
called AppState
, which helps me set the delegate, so they keyboard input can be different depending on the view currently showing.
protocol KeyInput {
func onKeyPress(_ key: String)
}
class KeyInputController<Content: View>: UIHostingController<Content> {
private let state: AppState
init(rootView: Content, state: AppState) {
self.state = state
super.init(rootView: rootView)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func becomeFirstResponder() -> Bool {
true
}
override var keyCommands: [UIKeyCommand]? {
switch state.current {
case .usingApp:
return [
UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(keyPressed(_:)))
]
default:
return nil
}
}
@objc private func keyPressed(_ sender: UIKeyCommand) {
guard let key = sender.input else { return }
state.delegate?.onKeyPress(key)
}
}
AppState (@EnvironmentObject
):
class AppState: ObservableObject {
var delegate: KeyInput?
/* ... */
}
And the scene delegate looks something like:
let stateObject = AppState()
let contentView = ContentView()
.environmentObject(stateObject)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = KeyInputController(rootView: contentView, state: stateObject)
/* ... */
}
This makes it really easy to now add functionality depending on the keys pressed.
Conform to KeyInput
, e.g.:
struct ContentView: View, KeyInput {
/* ... */
var body: some View {
Text("Hello world!")
.onAppear {
self.state.delegate = self
}
}
func onKeyPress(_ key: String) {
print(key)
guard key == UIKeyCommand.inputEscape else { return }
// esc key was pressed
/* ... */
}
}