Swift: support cut/copy/paste shortcuts in a NSOpenPanel
and NSSavePanel
July 27, 2023
NSOpenPanel
and NSSavePanel
July 27, 2023
Let’s consider this basic Swift app that simply shows an NSSavePanel
:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let app = NSApplication.shared
app.activate(ignoringOtherApps: true)
let savePanel = NSSavePanel()
savePanel.runModal()
}
}
let app = NSApplication.shared
let delegate = AppDelegate()
app.setActivationPolicy(.regular)
app.delegate = delegate
app.run()
This shows a generic open panel as expected:
However we have a problem: we can’t cut, copy or paste in of the text fields (Save As, Tags, Search). We can’t Command + X, C or V. All those shortcuts do is playing an annoying beep noise telling us we can’t do that.
This is because on macOS, those shortcuts are actually tied to menu items. You can’t have Command + C work unless you have a matching menu item, typically Edit > Copy.
To solve this, we’re gonna add an edit menu tour app with the proper shortcuts.
let mainMenu = NSMenu()
app.mainMenu = mainMenu
let appMenu = NSMenuItem()
mainMenu.addItem(appMenu)
let editMenu = NSMenuItem()
mainMenu.addItem(editMenu)
let editSubmenu = NSMenu(title: "Edit")
editMenu.submenu = editSubmenu
editSubmenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x")
editSubmenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c")
editSubmenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v")
editSubmenu.addItem(
withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
We now have a nice edit menu:
Here, we leverage automatic menu enabling in the action
in order to map
menu items and shortcuts to the first object in the responder chain that
implements the given action, as explained in this Stack Overflow post.
This is pretty neat, and thanks to this feature, we now have working cut/copy/paste in our dialog!
As a bonus, it would be a good practice to also add a way to quit our app using the same method:
let appSubmenu = NSMenu()
appMenusubmenu = appSubmenu
appSubmenu.addItem(
withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")