Chapters

Hide chapters

Catalyst by Tutorials

Third Edition · iOS 15 · Swift 5.6 · Xcode 13.3

6. The Keyboard
Written by Andy Pereira

The physical keyboard is something that has always been part of the personal computer experience. But, when iOS was introduced, it almost seemed like the idea of a software keyboard would take over. Luckily, for those that like a real keyboard, Apple introduced support not only for physical keyboards on iOS but keyboard shortcuts.

While keyboards shortcuts might go overlooked on iOS apps, macOS users expect them. In this chapter, you’ll learn how to add shortcuts, what modifier keys are, and how to combine them with key combinations to quickly perform tasks in your app.

Getting Started

Open the starter project for this chapter. It’s best if you can run this app on a physical iOS or iPadOS device, with a physical keyboard paired with the device. Using the simulator works too, but you may notice some performance issues.

First Responders

Before you add the shortcuts, it will help to understand a little bit about the responder chain and the first responder. UIViewControllers, UIViews and UIApplication are all classes that can receive and handle events, otherwise known as responder objects.

Since you can add keyboard shortcuts to any of these kinds of classes, you’ll need to tell the system which responder is the class in the responder chain that will receive the keyboard event first. This is referred to as the first responder.

To start, open RootSplitViewController.swift and add the following:

override var canBecomeFirstResponder: Bool {
  true
}

Now, when you perform a keyboard shortcut, RootSplitViewController is able to receive and respond to it.

Adding the Commands

Keyboard shortcuts can be performed by simply pressing a single key on the keyboard or multiple keys. They can also be used in conjunction with modifiers keys, like Command, Control and Option. You’re adding three shortcuts that each use modifier keys.

Open RootSplitViewController.swift and add the following:

@objc private func addEntry(sender: UIKeyCommand) {
}

@objc private func goToPrevious(sender: UIKeyCommand) {
}

@objc private func goToNext(sender: UIKeyCommand) {
}

For now, these don’t do anything. You need them because keyboard shortcuts need actions to call. You’ll come back to this later in the chapter.

Now you start adding key commands. Add the following in RootSplitViewController.swift:

override var keyCommands: [UIKeyCommand]? {
  let newKeyCommand = UIKeyCommand(
    input: "N",
    modifierFlags: .control,
    action: #selector(addEntry(sender:))
  )
  newKeyCommand.discoverabilityTitle = "Add Entry"
  return [newKeyCommand]
}

Here, you’ve added a key command for adding a new entry. It’s important to know what’s happening:

  • input: This is the actual key that will trigger the action to be called.
  • modifierFlags: In this example, you’ve added .control, which means your shortcut will be performed by pressing Control-N.
  • action: This is the method called when the shortcut is performed.
  • discoverabilityTitle: Holding down Command shows the Discoverability window or layer. It lists all the keyboard shortcuts available in the current application. Adding the discoverabilityTitle is required to list the shortcut in the Discoverability window.

Overriding keyCommands is how you inform the responder chain what key commands the current responder supports. It is important to note that the system reserves certain key commands for itself, which you may not want to override. For example: Cut, Copy and Paste’s key combinations are reserved for the system and you may not want to change these for yourself. Because Command-N is reserved for opening a new window, you use the modifier to make Control-N create the entry.

Note: wantsPriorityOverSystemBehavior is UIKeyCommand’s new property, introduced in iOS 15. This property is false by default. When set to true, it prioritizes key commands over text input or focus movements.

Build and run, and hold down Command to make the Discoverability window appear.

Discoverability window appears.
Discoverability window appears.

If you are having trouble with keyboard shortcuts within the simulator, you may need to use the Capture Keyboard functionality. You can select this in the toolbar of your iPad simulator, as shown below:

Capture Keyboard in iPad simulator.
Capture Keyboard in iPad simulator.

To exit keyboard capture mode, you can select the button again, or press Escape.

Note: When you run on an iPad simulator, because of the way the app is designed, it’s best to work on this chapter with simulator in Landscape orientation.

To get the application to add an entry when using the key combination, add this line to addEntry:

DataService.shared.addEntry(Entry())

Build and run, and then press Control-N. You now see a new entry shows in the list.

To set the remaining key commands, replace keyCommands with the following:

override var keyCommands: [UIKeyCommand]? {
  let newKeyCommand = UIKeyCommand(
    input: "N",
    modifierFlags: .control,
    action: #selector(addEntry(sender:))
  )
  newKeyCommand.discoverabilityTitle = "Add Entry"
  let upKeyCommand = UIKeyCommand(
    input: "[",
    modifierFlags: [.command, .shift],
    action: #selector(goToPrevious(sender:))
  )
  upKeyCommand.discoverabilityTitle = "Previous Entry"
  let downKeyCommand = UIKeyCommand(
    input: "]",
    modifierFlags: [.command, .shift],
    action: #selector(goToNext(sender:))
  )
  downKeyCommand.discoverabilityTitle = "Next Entry"
  return [newKeyCommand, upKeyCommand, downKeyCommand]
}

The final two key commands allows the user to select the previous or next entry in the entry list. While almost the same as newKeyCommand, these take multiple modifier keys to perform. It requires Command-Shift-[ to be pressed to go back in the list, and Command-Shift-] to move forward.

While you can decide which modifiers are used, remember to keep the combinations simple enough for your user to remember.

Build and run, and hold down Command to show the Discoverability window. You now see all three of your keyboard shortcuts:

All shortcuts shown in Discoverability window.
All shortcuts shown in Discoverability window.

Now, your last step is to actually do something when the keys are pressed. Still continuing in RootSplitViewController.swift, add this code to goToPrevious(sender:):

guard let navigationController =
  viewControllers.first as? UINavigationController,
  let mainTableViewController =
  navigationController.topViewController
    as? MainTableViewController else { return }
mainTableViewController.goToPrevious()

The code above calls main view controller’s method to go the previous entry in the list.

And add this code to goToNext(sender:):

guard let navigationController =
  viewControllers.first as? UINavigationController,
  let mainTableViewController =
  navigationController.topViewController
    as? MainTableViewController else { return }
mainTableViewController.goToNext()

Similarly, you can now go to the next entry in main table view controller’s list.

Now, open MainTableViewController.swift, and add the following:

func goToPrevious() {
  guard let index = indexOfCurrentEntry(),
    index > 0 else { return }
  let previousIndex = index - 1
  let indexPath = IndexPath(
    row: previousIndex,
    section: 0
  )
  tableView.selectRow(
    at: indexPath,
    animated: false,
    scrollPosition: .middle
  )
  performSegue(
    withIdentifier: "ShowEntrySegue",
    sender: tableView.cellForRow(at: indexPath)
  )
}

func goToNext() {
  guard let index = indexOfCurrentEntry(),
    index < DataService.shared.allEntries.count - 1
  else { return }
  let nextIndex = index + 1
  let indexPath = IndexPath(
    row: nextIndex,
    section: 0
  )
  tableView.selectRow(
    at: indexPath,
    animated: false,
    scrollPosition: .middle
  )
  performSegue(
    withIdentifier: "ShowEntrySegue",
    sender: tableView.cellForRow(at: indexPath)
  )
}

When performing the Previous Entry and Next Entry shortcuts, these methods will handle going to the previous and next entries.

Build and run, and use the keyboard shortcuts. Press Control-N to add a few entries, and Command-Shift-[ and Command-Shift-] to move through the list.

Last, add the final keyboard shortcut to delete an entry. Back in RootSplitViewController.swift, add the following at the end of keyCommands replacing the existing return line:

let deleteKeyCommand = UIKeyCommand(
  input: "\u{8}",
  modifierFlags: [.command],
  action: #selector(removeEntry(sender:))
)
deleteKeyCommand.discoverabilityTitle = "Delete Entry"

return [
  newKeyCommand,
  upKeyCommand,
  downKeyCommand,
  deleteKeyCommand
]

This adds the shortcut to delete an entry when pressing Command-Delete. To display the macOS system symbol for Delete, and respond to it being pressed, you use the Unicode symbol for BACKSPACE, which is 8.

Then, add the following in the same file:

@objc private func removeEntry(sender: UIKeyCommand) {
  guard let navigationController = viewControllers.first
    as? UINavigationController,
    let mainTableViewController
      = navigationController.topViewController
      as? MainTableViewController else { return }
  mainTableViewController.deleteCurrentEntry()
}

deleteCurrentEntry does the actual work of deleting the selected entry in the list.

Finally, add the following to MainTableViewController.swift:

func deleteCurrentEntry() {
  guard let index = indexOfCurrentEntry() else { return }
  DataService.shared.removeEntry(atIndex: index)
  var indexPath = IndexPath(
    row: index,
    section: 0
  )
  guard tableView.numberOfRows(inSection: 0) > 0 else {
    performSegue(withIdentifier: "ShowEntrySegue", sender: nil)
    return
  }
  if index == tableView.numberOfRows(inSection: 0) {
    indexPath = IndexPath(
      row: index - 1,
      section: 0
    )
  }
  tableView.selectRow(
    at: indexPath,
    animated: false,
    scrollPosition: .middle
  )
  performSegue(
    withIdentifier: "ShowEntrySegue",
    sender: tableView.cellForRow(at: indexPath)
  )
}

The last thing you’ve added here supports responding to the keyboard shortcut and deleting the entry that is selected in the table view.

The examples in this chapter used an input that directly relates to what you see on the keyboard or its Unicode symbol. However, there are constants defined for a few keys which you’ll need to use if you want to respond to Escape or any of the directional arrows:

  • UIKeyCommand.inputUpArrow
  • UIKeyCommand.inputDownArrow
  • UIKeyCommand.inputLeftArrow
  • UIKeyCommand.inputRightArrow
  • UIKeyCommand.inputEscape

Alternate Keyboard Handling

The previous section handled adding keyboard shortcuts in a way that enabled discoverability for the user. However, you may not want to inundate your users with all the key commands you would like to support, especially if there are multiple options available.

Open MainTableViewController.swift, and add the following to the main class body:

override var canBecomeFirstResponder: Bool { true }

override func pressesBegan(
  _ presses: Set<UIPress>,
  with event: UIPressesEvent?
) {
  for press in presses {
    guard let key = press.key else { continue }
    switch key.keyCode {
    case .keyboardUpArrow,
         .keyboardLeftArrow: goToPrevious()
    case .keyboardDownArrow,
         .keyboardRightArrow: goToNext()
    default:
      super.pressesBegan(presses, with: event)
    }
  }
}

This is an older way to handle keyboard input. Here, you’re simply listening for when keyboard presses begin and respond to the desired keyCodes. In this example, you’ve added the ability to use the up, down, left and right keys to cycle through entries in the sidebar.

Key Points

  • Providing keyboard shortcuts is essential for macOS users, and is becoming more expected for iPadOS users.
  • UIKeyCommand makes setting up keyboard shortcuts easy, and works across iOS and macOS.
  • Ensure you handle typical shortcuts for keys that aren’t automatically supported by the operating system.

Where to Go From Here?

Your app is now set to handle shortcuts. While Catalyst will automatically handle these keyboard shortcuts, you’ll learn later, how to make sure these shortcuts are shown in the Menu bar.

You can learn more about UIKeyCommand on Apple’s website.

There’s more you can learn about the responder chain, as well, from Apple’s website.

Also, check out Apple’s documentation, to learn more on wantsPriorityOverSystemBehavior, the new property introduced in iOS 15.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.