Weekly Swift articles, podcasts and tips by John Sundell.

Handling keyUp and keyDown events on iOS 13.4 and later

Published on 06 Apr 2020

In iOS 13.4 Apple (finally) added support for detecting when an external keyboard key is either pressed or released, which can be incredibly useful in a number of different situations. While it was previously possible to register keyboard shortcuts using the UIKeyCommand API, we now get direct access to what’s commonly referred to as keyUp and keyDown events.

As an example, let’s say that we’re working on an app that includes some form of editor, and that we want to add richer support for controlling that editor using an external keyboard. The first thing that we’ll need to do in order to make that happen is to override three methods that’ll let us handle UIPress events — since that class has now been upgraded with a new key property, which gives us information about pressed (and released) keys.

We can override those three methods on any UIResponder, for example our EditorViewController, and within each implementation we’ll unwrap the new key property in order to pass its UIKey value to one of two private methods — like this:

class EditorViewController: UIViewController {
    ...

    override func pressesBegan(_ presses: Set<UIPress>,
                               with event: UIPressesEvent?) {
        super.pressesBegan(presses, with: event)
        presses.first?.key.map(keyPressed)
    }

    override func pressesEnded(_ presses: Set<UIPress>,
                               with event: UIPressesEvent?) {
        super.pressesEnded(presses, with: event)
        presses.first?.key.map(keyReleased)
    }

    override func pressesCancelled(_ presses: Set<UIPress>,
                                   with event: UIPressesEvent?) {
        super.pressesCancelled(presses, with: event)
        presses.first?.key.map(keyReleased)
    }
}

Another option would be to implement the above within a dedicated “keyboard handling” child view controller, which we could then plug into our main EditorViewController.

Next, let’s implement those private methods, starting with keyPressed. The cool thing about UIKey is that it isn’t just a numeric key code (which is how these things have often worked historically), it’s a class that contains all sorts of useful information about a key’s state — for example whether a modifier key is also currently pressed. It also provides a completely type-safe API for identifying keys, which enables us to implement our handling logic using a single switch statement:

private extension EditorViewController {
    func keyPressed(_ key: UIKey) {
        switch key.keyCode {
        case .keyboardP:
            selectTool(.pen)
        case .keyboardB:
            selectTool(.brush)
        case .keyboardE where key.modifierFlags.contains(.command):
            eraseSelection()
        case .keyboardLeftShift, .keyboardRightShift:
            activateBoxedSelectionMode()
        default:
            break
        }
    }
}

Finally, let’s also implement our keyReleased method using the exact same technique:

private extension EditorViewController {
    func keyReleased(_ key: UIKey) {
        switch key.keyCode {
        case .keyboardLeftShift, .keyboardRightShift:
            deactivateBoxedSelectionMode()
        default:
            break
        }
    }
}

Apple might’ve taken quite a long time to add full keyboard event support to iOS, but now that it’s finally here, its API is incredibly nice.