Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Key press events detection in SwiftUI

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 do you stay current as a Swift developer?

Let me do the hard work and join 19,138 developers that stay up to date using my weekly newsletter:

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:

A focused view receives key events but might show an unwanted focused effect.
A focused view receives key events but might show an unwanted focused effect.

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!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.