TL;DR for impatient developers:

@IBAction func browseDirectory(_ sender: Any) {
    
    let dialog = NSSavePanel()
    
    dialog.title = "Save file"
    dialog.showsResizeIndicator = true
    dialog.canCreateDirectories = true
    dialog.showsHiddenFiles = true
    dialog.allowedFileTypes = ["txt"]
    
    if (dialog.runModal() == NSApplication.ModalResponse.OK) {
        let result = dialog.url
        
        if (result != nil) {
            let path = result!.path // Do stuff with 'path' variable
        }
    } else {
        return // User clicked cancel
    }
}

Don’t forget to allow the app to access the filesystem.


Okay, we need to make a dialog, so let’s make a variable named dialog and assign it the NSSavePanel.

let dialog = NSSavePanel()

So far so good. Then we have to set some parameters. Let’s give the dialog a title-bar name.

dialog.title = "Save my stash of file(s)"

The parameter names are pretty self-explanatory, but these options are for showing the “resize” button in the title bar, allowing users to create new folders, and showing hidden files.

dialog.showsResizeIndicator = true
dialog.canCreateDirectories = true
dialog.showsHiddenFiles = true

Good, but what if you want to restrict users so that they can only use .js forever? we’re horible, horrible people

dialog.allowedFileTypes = ["js"]

Now, we show the dialog to the users.

if (dialog.runModal() == NSApplication.ModalResponse.OK) {
    let result = dialog.url
    
    if (result != nil) {
        let path = result!.path // Do stuff with 'path' variable
    }
} else {
    return // User clicked cancel
}

Great, so we’re done now, right? But now, you get this when you execute the app:


What is this? Well, you forgot to allow the app access to your filesystem! Simply click on your project on the left sidebar, press on your app under the smaller sidebar with the title-name “Targets”, choose “Capabilities” on the top bar, and then choose Read/Write under “User Selected File”.

xcode_nssavepanel_permissions

The end!