AppKit provides built in functionality for automatically handling keyboard focus on a window via two methods:
It is important to know that macOS by default does not add all controls to the key view loop. In order to enable that, you must enable the user setting at: System Preferences -> Keyboard -> Shortcuts -> Use Keyboard Navigation to move focus between controls
It is recommended to let the window automatically calculate your key view loop for you. There are very few cases in which the automatically calculated key view loop is not sufficient, and in most cases the built in arrow key navigation of the AppKit controls mentioned above are a sufficient supplement.
Allowing the window to automatically calculate the key view loop (by setting window.autoRecalculatesKeyViewLoop = true
), you can:
One thing to note: setting autoRecalculatesKeyViewLoop
to true
means that any NSView whose nextKeyView
property you have manually set will be ignored. This is seemingly because AppKit will constantly recalculate the key view loop every time a view is added or the user tabs to a new view, stamping over any customizations to the key view loop done by the developer.
Manually managing your own key-view loop (by setting window.autoRecalculatesKeyViewLoop = false
) has a few major downsides:
nextKeyView
property of in your project.window.recalculateKeyViewLoop()
will stamp over your customizations, as we have already described.Rather than modifying the key view loop, consider if there is an existing AppKit control that could fit your needs. Both NSTableView and NSCollectionView will allow you to create a list / grid of views and use keyboard arrow keys to move selection between them. This plays nicely with the key view loop, as the entire table (in the case of NSTableView) is what receives focus, while the arrow keys select the individual row / element.
If your window has a fairly static view hierarchy, it may be simple enough to set the nextKeyView properties for every applicable view. If you only have a few specific views whose nextKeyView
property you want to modify, you can call window.recalculateKeyViewLoop()
once to get an AppKit generated key view loop, and then set up your custom key views.
To turn the automatic key view loop off:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillFinishLaunching(_ notification: Notification) {
// ... window setup code
window.autorecalculatesKeyViewLoop = false
}
}
Setting up your custom nextKeyView properties in your ViewController:
// Good: set the next key view in viewDidAppear() after a call to window.recalculateKeyViewLoop()
class TestViewController: NSViewController {
override func viewDidAppear() {
// Let AppKit generate a key view loop for you
view.window?.recalculateKeyViewLoop()
// Set up your own custom nextKeyViews
fooView.nextKeyView = barView
// Any subsequent call to window.recalculateKeyViewLoop()
// will overwrite the nextKeyView of fooView
}
}
// Bad: fooView.nextKeyView is immediately overwritten by the subsequent call to window.recalculateKeyViewLoop()
class TestViewController: NSViewController {
override func viewDidAppear() {
fooView.nextKeyView = barView
view.window?.recalculateKeyViewLoop()
}
}
// Bad: Setting the nextKeyView in viewDidLoad will do nothing, as the window is not guaranteed to be available yet
class TestViewController: NSViewController {
override func viewDidLoad() {
view.window?.recalculateKeyViewLoop()
fooView.nextKeyView = barView
}
}