Save And Open Panels in macOS Apps

March 18th, 2021

⏱ Reading Time: 8 mins

Among the most common tasks when developing macOS applications is to allow users to save data in files, and loading back from them. In order to persist the familiar experience that users already have when using their Macs, developers usually employ system provided user interfaces that allow to choose where to save or load to and from in the disk. Those standard interfaces are known as save and open panels. In this post I will show you how to configure and present both of them in a storyboard based macOS app.

There is one thing to keep in mind before keep reading; both save and open panels are not meant to store or load data to and from files respectively. Their purpose is solely to return one or more URL objects that users compose by choosing directories and file names through a graphical interface.

That being said, suppose that we have the following really simple text edit application:

Sample text edit application

It has three buttons; one for clearing the textview and start writing from scratch, and two more for saving the current text, or loading one by opening a file in the hard drive. At the time being none of these works, and that’s what I’ll demonstrate how to fix here.

The Save panel

Focusing on the save button initially, the goal here is to present the system’s save panel in order to let users set a name and choose a target directory for the file that will be containing text from the textview.

The first step towards that is to initialize a NSSavePanel object in the method that implements the save functionality:

There are several properties that we can configure in the savePanel object. The first and most important one is to specify the allowed file types, meaning the kind of files that users can create. What you will actually set here depends totally on the application you are making and the kind of files it deals with. In this example I have a simple text editor, so I want to save content in plain text files with the txt extension. Here is how I indicate that:

See that the provided value to the allowedFileTypes property is an array of string values. Each value must match to the file extension of the kind of file or files we want to provide saving support for.

Sometimes declaring just the allowed file is enough, without being necessary to specify explicit values to other properties. However, there are a few more that you will definitely find interesting. The next one indicates whether users can create new folders if they need so or not through the save panel:

By setting true to the above property, an additional button will appear in the save panel titled New Folder.

It’s also possible to decide whether the file extension will be visible or not next to the file name. You might want it present if you have more than one allowed file types, as extension is not visible by default:

Users can type an extension along with the file name other than the expected one(s). However, such an action is prevented by default, but if you want to change it you can do so with the following property:

Note once again that when allowsOtherFileTypes is true then users can type any file extension they want right next to the name. Be cautious with that, especially if you’re going to give the capability of presenting an open panel for locating already stored files.

Depending on how we will present the save panel (more on that in a moment), it may have a title or not, and in any case it can display a message to users. It’s also possible to override the default Save title in the save button, and replace it with a custom prompt. Here’s how we do all that:

Besides those, we can also customize the displayed text next to the file name textfield, as well as the default file name. If they are not provided, Save as: and Untitled are the default values respectively:

The above are not the only properties that we can configure in a save panel. However, they are the most common ones, and rarely you’ll ever need more than those. But if you do, Xcode auto-suggestion is your friend; it will list all properties and methods available to use.

Presenting a save panel

Presenting a save panel can be done in two ways; either as a modal window, or as a modal sheet. When presented as a window, the panel’s title is visible to the window’s bar. If presented as a sheet, then the title is not displayed.

Starting with the former, presenting a save panel modally as a window requires to call a method named runModal():

The returned value from that method is a NSApplication.ModalResponse object. In the above snippet, it’s stored in the response constant. It contains the button that user clicked on.

To have the save panel shown as a sheet is a bit different than as a window:

The beginSheetModal(for:completionHandler:) method expects as first argument the window that will be presented as a sheet on. Getting the current window is done using the window property of the view controller’s view. However, that returns an optional value, so unwrapping it before using it is the proper way to go. That ensures that if for some reason the window cannot be fetched, the app will not crash; the save panel simply will not be shown. The unwrapped window object is the given value as first argument eventually.

The second argument is a completion handler; a closure that has one property only. The same response object that contains the selected by the user button.

Regardless of how the save panel will be presented, the actual goal is the same; to get the selected URL that the user has formed by providing a file name and choosing a target directory, ensuring first though that the save button was clicked. Remember that the save panel does not perform any actual saving; it’s there just to let users specify a desired directory and file name. Performing the actual save task is still up to us and depends on the kind of the data that we need to store.

Just a little bit above I demonstrated the response object that contains the user selected action. To determine if the save button was eventually clicked we can do the following:

Alternatively, you can use the traditional if statement.

Next, getting the target URL is pretty easy and it’s done through the save panel instance. Note that it’s an optional value, so it must be unwrapped before used:

The above two guard statements can be combined into one:

Finally, if the code execution continues normally without falling to the else case, it’s time to perform the actual writing to file. In the sample case I’m demonstrating here, this is done as so:

Here’s a save panel as a modal window:

Save panel window

The panel can be expanded or collapsed. When expanded, the additional button to create new folders is revealed, as well as the capability to navigate among folders.

Save panel window expanded

Right next you can see the save panel as a sheet:

Save panel as sheet

See that it has no many differences comparing to the modal window; it’s mainly the title that’s missing. Display position is also different in macOS versions prior to Big Sur; in that case, the sheet slides in from the top side of the window, something that does not seem to be happening in Big Sur.

Right next you can find the entire demo method that implements the save panel:

Important Note: Before using the save panel, it’s mandatory to assign write permissions to your app. To do that, open the Signing & Capabilities tab for your project target, and then under the App Sandbox select the Read/Write option for the User Selected File type.

Sandbox settings

The Open panel

Initializing, configuring, and finally presenting an open panel is similar to the save panel that was previously described. Some properties already shown above exist here too. There are also other properties specific to the open panel.

However, before talking about them, let’s take things from the beginning. In order to use an open panel it’s necessary to create an instance of it first. At the majority of the cases, you will need to specify the allowed file types that can be selected through the panel using the allowedFileTypes property that we met before:

Now, among the various properties we can access through the NSOpenPanel instance there are some that we need to specify more often than others. The first one indicates whether users are allowed to select multiple files or not. If so, then multiple URLs are returned by the panel, otherwise it’s just a single URL object. The following command disables multiple file selection:

Besides that, and depending on the app you are making, you might want to let users select entire directories, or just prevent them from doing so:

Similarly, we can specify if selecting files will be allowed or not. That property can be combined with the previous one in order to allow selection of a specific kind of items only:

Besides the open panel’s configuration, the ways it can be presented are exactly the same to those I discussed about the save panel. It can be shown either as a modal window, or as a modal sheet. Right below I’m presenting it as a window, and requesting for a single URL. Then, I load the file contents to the text view:

If you allow multiple file selection and you want to get back all URLs selected by the user, then instead of the url property shown above, use the urls; it returns an array of URL objects:

The entire demonstrative method implementation is this:

Load panel

Summary

Presenting a save or an open panel is an easy job that involves standard steps in order to achieve it. In this post I mentioned the most common properties one can set, but feel free to explore what other options exist there for you. Lastly, keep in mind that both panels consist of a basic experience that users are well familiar with, so don’t hesitate to use them whenever it’s appropriate to let users choose save locations or select files to open.

You can download the sample project demonstrated in this post from this link.

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.