Working with Open and Save Panels in Mac Apps
My short article on changing the button title for open and save panels gave me the idea to write a longer article on working with open and save panels.
You May Not Need to Deal with Open and Save Panels
Apple’s document architecture gives you a lot of default behavior for free. You can write an app that opens and saves files without having to write any code for the open and save panels. The most common case of having to write code that involves open and save panels is importing and exporting data. For example an image editor would use a save panel to export the image to common file formats, such as GIF, JPEG, and PNG.
Creating the Panel
Creating the open or save panel is the easiest step. Call the constructor for NSOpenPanel
or NSSavePanel
.
let savePanel = NSSavePanel()
Customizing the Panel’s Appearance
When you create an open or save panel, you get the default appearance, which works well most of the time. But if you’re using a save panel to export a file, you want the button to say Export instead of Save because you’re exporting a file. AppKit provides several ways to customize the appearance of open and save panels. Some of the most popular ways include the following:
- The
prompt
property lets you change the text of the default button. - The
title
property lets you change the title of the panel. - The
nameFieldLabel
property lets you change the label text in front of the filename text field. - The
nameFieldStringValue
property lets you set the filename currently shown in the filename text field. - The
directoryURL
property lets you set the current directory for the panel. - The
accessoryView
property lets you add an accessory view. A common use of an accessory view is to show a popup button with a list of file types. - The
allowedFileTypes
property lets you limit the file types you can open or save.
Custom Appearance Example
As an example, I’m going to share the code for a custom save panel for an app I’m developing. In this project I’m using a save panel to publish an EPUB book.
func buildPublishSavePanel() -> NSSavePanel {
let savePanel = NSSavePanel()
savePanel.title = “Publish Book”
savePanel.nameFieldLabel = “Book Name:”
savePanel.nameFieldStringValue = self.displayName
savePanel.prompt = “Publish”
savePanel.allowedFileTypes = [“epub”]
return savePanel
}
Because the save panel is for publishing books, I changed the panel’s title, name field label, and prompt (button text) to let people know this panel is for publishing books. I’m publishing EPUB books so I’m limiting the file types to files with the epub
extension.
The most confusing line of code is the line that sets the text field value to self.displayName
. The display name is the filename of the document that I’m publishing.
Showing the Panel
There are two ways to show an open or save panel. The easiest way is to show a panel as a modeless window. Call the panel’s begin
method and supply a closure.
savePanel.begin { (result: NSApplication.ModalResponse) -> Void in
// Code to come later
}
The other way is to show the panel as a sheet inside the document window. Call the panel’s beginSheetModal
method. Supply the window and a closure.
savePanel.beginSheetModal(for: window) {
(result: NSApplication.ModalResponse) -> Void in {
// Put your code here
}
}
Handling the Default Button Click
After showing the panel, you must handle the clicking of the default button (Open for an open panel, Save for a save panel). What you must do is check if the result of the panel running is NSFileHandlingPanelOKButton
, which occurs when someone clicks the default button. Usually you will call a function that performs the task you want to do. For example if you were exporting a file, you would call a function that exports the file.
The Full Example
The following code shows the full example of using a save panel to publish a book:
func showPublishSavePanel() {
let savePanel = buildPublishSavePanel()
savePanel.begin { (result: NSApplication.ModalResponse) -> Void in
if result.rawValue == NSFileHandlingPanelOKButton {
if let panelURL = savePanel.url {
// Replace this code with
// a call to a function you wrote.
publishEPUB(location: panelURL)
}
}
} // End of closure
}
The code begins by calling the buildPublishSavePanel
function I wrote earlier to create the save panel and customize how it looks.
The next line of code calls the save panel’s begin
method to show the save panel. The closure in the begin
method uses a variable, result
that stores the result of someone interacting with the save panel.
The first if
statement checks if the person clicks the default (Publish) button. If so, the second if
statement runs.
The second if
statement gets the URL of the location the person chose to store the published book and calls a function to publish an EPUB book at the supplied URL. In your app you would call a function you wrote that creates (examples: importing or exporting a file) a file at the given URL.