Handling keyboard events in iOS

In today's digital age, mobile apps are more popular than ever, and developers are constantly striving to improve user experience by incorporating new features and technologies. One such feature that can greatly enhance the usability of an iOS app is keyboard support. By allowing users to interact with an app using a physical keyboard, developers can provide a more efficient and streamlined experience. In this blog post, we'll explore how to add keyboard support to an iOS app using the UIKey protocol and the pressesEnded function. I created a starter project, a simple app that displays a table view with a couple of sections.

In the next section, we'll dive deeper into the pressesEnded function and explore how it can be used to enhance the functionality of your iOS app. Whether you're a seasoned iOS developer or just getting started, adding keyboard support to your app can greatly improve the user experience and make your app stand out from the crowd. So, let's get started!

Handling key events

We will implement scrolling to sections by pressing letter keys corresponding to the letter displayed on the header and jumping up and down between sections by pressing arrow keys.

To achieve our goal, we need to override the pressEnded method of our view controller. The pressesEnded function is a method of the UIResponder class, which is responsible for handling events related to user input. Specifically, the pressesEnded function is called when a user lifts their finger off a key on a physical keyboard. By overriding this function we can react to keyboard events triggered by the user.

override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        for press in presses {
            if let key = press.key?.charactersIgnoringModifiers {
                switch key {
                case "a"..."z":
                    if let section = database.sections.firstIndex(where: { $0.letter.lowercased() == key }) {
                        scrollTo(section: section)
                    }
                    return
                case UIKeyCommand.inputUpArrow:
                    if let firstVisibleElementSection = tableView.indexPathsForVisibleRows?.first?.section,
                       firstVisibleElementSection != 0 {
                        scrollTo(section: firstVisibleElementSection - 1)
                    }
                    return
                case UIKeyCommand.inputDownArrow:
                    if let firstVisibleElementSection = tableView.indexPathsForVisibleRows?.first?.section,
                       firstVisibleElementSection < database.sections.count - 1 {
                        scrollTo(section: firstVisibleElementSection + 1)
                    }
                    return
                default:
                    continue
                }
            }
        }
        
        super.pressesEnded(presses, with: event)
    }
    
    private func scrollTo(section: Int) {
        tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section), at: .top, animated: true)
    }

Let's dive in!

The code begins by iterating through each UIPress object in the presses set. For each UIPress, the code checks if the key associated with the press has any modifier characters (e.g. shift, option, command) and removes them. This is done by using the charactersIgnoringModifiers property of the UIPress's key property.

The code then uses a switch statement to handle three cases of key presses: lowercase alphabetic keys, the up arrow key, and the down arrow key. You can see, that we used a neat trick for letters - case with range! Instead of making 26 separate case statements for every letter, we can condense them into one elegant line of code!

In the case of a letter, the code checks if the database has a section whose letter property matches the key. If it does, the function calls the scrollTo(section:) method with the index of the matched section.

In the case of the up arrow key, the code checks if we are able to scroll the table view up. If yes, the code scrolls one section up.

In the case of the down arrow key,  the code checks if we are able to scroll the table view down. If yes, the code scrolls one section downwards.

If no matches were found, we should call super.pressesEnded(presses, with: event), this will propagate events up the responder chain, allowing other classes to handle events on their own.

Next steps

Sky is a limit. You can implement custom navigation with arrows and tab keys, and enable custom key combinations for functionalities. Many streaming apps implement left/right arrow actions to seek when playing content, which feels very natural when using iPad as a streaming device. Spacebar for pause, up/down for playback speed, the tab for jumping to the next episode of your favorite soap opera. We are limited only by our imagination!