Home iOS & Swift Books Catalyst by Tutorials

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 the chapter. It will be 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 will work, 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.

// MARK: - Keyboard Commands
override var canBecomeFirstResponder: Bool {
  true
}

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 going to add three shortcuts that each use modifier keys.

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

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

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

DataService.shared.addEntry(Entry())
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]
}

guard let navigationController =
  viewControllers.first as? UINavigationController,
  let mainTableViewController =
  navigationController.topViewController
    as? MainTableViewController else { return }
mainTableViewController.goToPrevious()
guard let navigationController =
  viewControllers.first as? UINavigationController,
  let mainTableViewController =
  navigationController.topViewController
    as? MainTableViewController else { return }
mainTableViewController.goToNext()
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))
}
let deleteKeyCommand
  = UIKeyCommand(input: "\u{8}",
                 modifierFlags: [.command],
                 action: #selector(removeEntry(sender:)))
deleteKeyCommand.discoverabilityTitle = "Delete Entry"

return [newKeyCommand, upKeyCommand, 
  downKeyCommand, deleteKeyCommand]
@objc private func removeEntry(sender: UIKeyCommand) {
  guard let navigationController = viewControllers.first
    as? UINavigationController,
    let mainTableViewController 
      = navigationController.topViewController
      as? MainTableViewController else { return }
  mainTableViewController.deleteCurrentEntry()
}
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))
}

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.

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)
    }
  }
}

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 on how to make sure these shortcuts are shown in the Menu bar.

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.