'SwiftUI iOS - how to capture hardware key events
I’m new to iOS development. Following a tutorial I have created a simple calculator using SwiftUI.
I have a keyboard attached to my iPad, and I would like to be able to enter values using the keyboard.
How can I capture and handle hardware keyboard events in a SwiftUI app (with no text field) ? I have tried to use the keyCommands on the SceneDelegate (UIResponder) as shown here, but that doesn’t work for me. As soon as I press any key on my iPad, I get “Connection to deamon was invalidated” in the XCode trace view.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
override var canBecomeFirstResponder: Bool {
return true;
}
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(input: "a", modifierFlags: [], action: #selector(test)),
UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(test))
]
}
@objc func test(_ sender: UIKeyCommand) {
print("test was pressed")
}
Thanks
Solution 1:[1]
It needs to override hosting view controller instead and all works. Tested with Xcode 11.2 / iOS 13.2
Here is example code
class KeyTestController<Content>: UIHostingController<Content> where Content: View {
override func becomeFirstResponder() -> Bool {
true
}
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(input: "1", modifierFlags: [], action: #selector(test)),
UIKeyCommand(input: "0", modifierFlags: [], action: #selector(test)),
UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(test))
]
}
@objc func test(_ sender: UIKeyCommand) {
print(">>> test was pressed")
}
}
and somewhere in SceneDelegate
below
window.rootViewController = KeyTestController(rootView: contentView)
Solution 2:[2]
If you're using the SwiftUI lifecycle @Asperi also shows how to access the rootViewController
in this post - Hosting Controller When Using iOS 14 @main.
In summary, HostingWindowFinder
tracks down the mutable version of the rootViewController
and provides access to it.
struct ContentView: View {
var body: some View {
Text("Demo Root Controller access")
.withHostingWindow { window in
window?.rootViewController = KeyController(rootView: ContentView())
}
}
}
extension View {
func withHostingWindow(_ callback: @escaping (UIWindow?) -> Void) -> some View {
self.background(HostingWindowFinder(callback: callback))
}
}
struct HostingWindowFinder: UIViewRepresentable {
var callback: (UIWindow?) -> ()
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
Also
If you want to just monitor raw keyboard input use pressesBegan(...)
- WWDC 2019 Talk
class KeyController<Content>: UIHostingController<Content> where Content: View {
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
for press in presses {
guard let key = press.key else { continue }
print(key)
}
}
}
Big thanks to @Asperi! ??
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | tyirvine |