Key press events detection in SwiftUI allows you to respond to a keyboard key like return (enter), shift, command, and more. While mostly Mac apps use keyboard events, you must consider adding support for iPad apps since external keyboards can be used.
SwiftUI offers several modifiers to listen to key press events, making it effortless for you to hook up actions to specific keyboard keys. Let’s dive in!
Responding to key press events
SwiftUI offers several onkeyPress
modifiers that allow you to listen to either specific or a set of keys. Before we dive into examples, it’s important to realize your view has to be in focus in order to receive events.
The following view demonstrates a plain Text view without any focus modifiers. The onKeyPress
event won’t work:
struct KeyboardEventsListenerView: View {
var body: some View {
Text("Let's listen to keyboard events!")
.padding()
.onKeyPress(.return) {
/// Doesn't get called since the Text element isn't focused.
print("Return key pressed!")
return .handled
}
}
}
We can solve this by making the text element focusable and enabling its focused state on appearance:
struct KeyboardEventsListenerView: View {
@FocusState private var isFocused: Bool
var body: some View {
Text("Let's listen to keyboard events!")
.padding()
.focusable()
.focused($isFocused)
.onKeyPress(.return) {
print("Return key pressed!")
return .handled
}
.onAppear {
isFocused = true
}
}
}
How to listen to a specific key event?
You can listen to a specific key event using the following onKeyPress
modifier:
.onKeyPress(.return) {
print("Return key pressed!")
return .handled
}
How to listen to any key press events?
If you want to listen to any keyboard pressed event, you can use the following modifier:
.onKeyPress(action: { keyPress in
print("""
New key event:
Key: \(keyPress.characters)
Modifiers: \(keyPress.modifiers)
Phase: \(keyPress.phase)
Debug description: \(keyPress.debugDescription)
""")
return .handled
})
The modifier handler provides a keyPress variable containing the key press event information. The above code example conveniently prints out those properties, which results in the following print example:
New key event:
Key: S
Modifiers: EventModifiers(rawValue: 18)
Phase: .down
Debug description: KeyPress(.down, "S")
In this case, I’ve pressed SHIFT + CMD + S
. The debug print shows event modifiers as a raw value since we’re dealing with an OptionSet. You can use the characters
property to find the key and the phase
property to get the phase of the key-press event (.down
, .repeat
, or .up
).
Listening to key down, up, or repeat events
SwiftUI’s onKeyPress modifiers will only listen to key press events in the down phase by default. If you want to listen to key up events, you can use the phases property:
.onKeyPress(phases: .up, action: { keyPress in
print("Key \(keyPress.characters) released")
return .handled
})
You can also listen to repeating keyboard events. In this case, the handler will only be called when a user holds a keyboard event for a longer time:
.onKeyPress(phases: .repeat, action: { keyPress in
print("Key \(keyPress.characters) repeats!")
return .handled
})
How to listen to a specific set of characters?
You can listen to a specific set of characters to ignore other keys. For example, you might only want to listen to numeric key press events:
.onKeyPress(characters: .decimalDigits, phases: .up, action: { keyPress in
print("Released key \(keyPress.characters)")
return .handled
})
In the above code example, we only listen to decimal digits up events.
What to do when the onKeyPress modifier isn’t working?
You might not get any callbacks after using the onKeyPress
modifier to listen to key press events. As discussed earlier, it’s important to ensure the view containing the modifier is currently having focus. You can do this by using the focusable
() and focused($isFocused)
modifiers:
struct KeyboardEventsListenerView: View {
@FocusState private var isFocused: Bool
var body: some View {
Text("Let's listen to keyboard events!")
.padding()
.focusable()
.focused($isFocused)
.onKeyPress(characters: .decimalDigits, phases: .up, action: { keyPress in
print("Released key \(keyPress.characters)")
return .handled
})
.onAppear {
isFocused = true
}
}
}
After adding these modifiers, you might notice a focused effect around your view:
You can disable this effect using the .focusEffectDisabled()
modifier:
struct KeyboardEventsListenerView: View {
@FocusState private var isFocused: Bool
var body: some View {
Text("Let's listen to keyboard events!")
.padding()
.focusable()
.focused($isFocused)
.focusEffectDisabled()
.onKeyPress(characters: .decimalDigits, phases: .up, action: { keyPress in
print("Released key \(keyPress.characters)")
return .handled
})
.onAppear {
isFocused = true
}
}
}
You’ve now created a view that listens to numeric key-up events while not indicating being focused.
If a parent view listens to keyboard events, you might still not receive any key press events. Each onKeyPress
modifier handler must return a result, either .handled
or .ignored
. If a parent view returns .handled
, it will prevent child views from receiving the key press event.
Conclusion
Key press events detection in SwiftUI allows you to listen to either a specific or a set of keys. You can focus on up, down, repeat, or all phases and use the handler to perform any actions. If your view isn’t focused or if a parent view listens to key events, you might not be able to get any callbacks.
If you want to improve your SwiftUI knowledge, even more, check out the SwiftUI category page. Feel free to contact me or tweet me on Twitter if you have any additional tips or feedback.
Thanks!