Keyboard shortcuts and the iOS responder chain
When adding support for keyboard shortcuts, a seasoned iOS developer might raise an eyebrow when noticing that UIKeyCommand
takes a selector for an action, but no target. This is because the keyboard commands1 are not send directly to any of your classes, but they instead are passed along the responder chain – until a UIResponder
is found that responds to that selector.
The responder chain typically looks like in the following chart from Apple’s documentation:
Exemplary responder chain
To implement keyboard shortcuts, you need to implement keyCommands
at all the parts along your responder chain that should can provide keyboard shortcuts. The nice thing about is that the keyboard shortcuts available on a screen are all the shortcuts provided along the responder chain, so you can provider shortcuts specific to a view, view controller or globally for your application without any of those pieces being aware of the others. If there’s a clash in keyboard shortcuts then those of the responder further up in the chain are used.
What’s not obvious from the documentation is where the start is of your responder chain when it comes to keyboard shortcuts. If you hold the ⌘ key, what objects get to expose their keyboard shortcuts? By default this is just the bottom part of the figure above, but you can opt-in with your view controllers by implementing canBecomeFirstResponder()
. However with that any keyboard shortcuts provided by its view and subviews – for example a table view – won’t be available. This is where the concept of a first responder comes in. You can nominate a view by explicitly calling becomeFirstResponder()
on it and the chain will then start there.
In summary:
- Add your
keyCommands
along the responder chain; the keyboard shortcuts are then a union of all of these. - Keep in mind that the selector for a keyboard shortcut will fire for the first responder along that chain that handles it, and processing stops after that.
- Adjust the first responder where necessary.
Stumbling blocks
When you hit any stumbling blocks, the important thing to keep in mind that keyboard shortcuts are powered by the responder chain, so have a mental diagram of your app’s chain in your head and be aware that it changes with the current selection/first responder. This also means that when you hold the command key to see your keyboard shortcuts and then execute the command via the keyboard, there are two passes over the responder chain: First to get a list of the available commands, and then again the relevant selector is sent along the chain.
Only UIResponder
subclasses participate in this. You could have a class KeyCommandHelper: NSObject
that creates a list of shared keyboard shortcuts and exposes the relevant selectors. However when you return those from your view controller’s keyCommands
, the key commands themselves know nothing about who created them (i.e., and your KeyCommandHelper
). Your command show up when holding ⌘, but when pressing the relevant keyboard shortcuts a selector on your KeyCommandHelper
would never called.
So if you do want to have a class participate in this, you can either make it a subclass of UIResponder
and then insert the class into your responder chain2, or by implementing those shared keyboard shortcuts further app your responder chain, such as in your app delegate (make sure this then subclasses from UIResponder
).
Further reading
- The WWDC session 224 ‘Core iOS Application Architectural Patterns’ from 2014 has some good basics on the target/action pattern and the responder chain on iOS.
- NSHipster has more on
UIKeyCommand
and a nice intro to keyboard shortcuts. - Douglas Hill has a gist of a keyboard-enabled UITableView, which is a great starting point for adding keyboard shortcuts to your table view-based screens.
- Take a look at macOS’s standard keyboard commands so that you have consistency, especially when iOS apps will make it to macOS later this year.
-
More specifically the selectors that fire when the keyboard commands are pressed. ↩
-
See ‘Altering the Responder Chain’ in the documentation. Basically this means overriding
next
and maintaining (weak) references to the other objects in the responder chain. ↩