macOS and the Responder Chain
I was working on an app in SwiftUI today, and one of the problems I ran into was how to add a button to toggle the sidebar. You can add SidebarCommands
to your scene (via the .commands
modifier) which will add a “Toggle Sidebar” item to the “View” menu, but there didn’t look to be a built-in way to add a button to do the same thing to the toolbar.
Creating the button and adding it to the toolbar is fairly straightforward:
content
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar) {
Image(systemName: "sidebar.leading")
}
}
}
The challenge comes in what to do in the callback (toggleSidebar
) when the button is clicked. Many answers e.g. on Stack Overflow and the developer forums advocate for drilling down to the key window’s firstResponder
and asking it to toggle the sidebar. This is certainly better than drilling down the view hierarchy to try to find the split view itself, however there is an even easier way!
On macOS, the responder chain plays a much bigger role than it does on iOS. On iOS, you typically only care about it when you want to e.g. force a text field to become active or resign active (usually to dismiss the on-screen keyboard). On macOS, you deal with the responder chain much more frequently, especially when dealing with menus — as the first responder changes, menu items check if they are applicable or should be disabled; when a menu item is selected, its action is typically invoked via the responder chain.
Rather than calling
NSApp.keyWindow?.firstResponder?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)),
with: nil
)
(as is recommended many places), you can simply invoke the default handling the application uses to respond to menu item actions: NSApplication.sendAction(_:to:from:)
, and it will properly forward it to the appropriate place via the responder chain.
NSApp.sendAction(
#selector(NSSplitViewController.toggleSidebar(_:)),
to: nil,
from: nil
)
This is exactly what happens when the “Toggle Sidebar” menu item is selected in the “View” menu.
Update:
There is a lower-voted answer on the Stack Overflow question posted above that advocates for the same thing. Somehow, I missed that earlier.