SwiftUI on macOS equivalent of NSSavePanel for choosing a destination URL?

In AppKit, NSSavePanel can be used to ask the user for a destination URL before the app creates a file.

What is the SwiftUI equivalent for this?

.fileImporter covers the NSOpenPanel case well enough, but I have not found a SwiftUI API that matches the simple NSSavePanel case where the app only needs the URL.

There's a new API in .fileExporter that appears close, but it requires a non nil WritableDocument, and seems designed around SwiftUI performing the file export.

My use case is a macOS app that creates new documents backed by SQLite. SQLite needs a file path so it can create the database at that location. With NSSavePanel, I can ask the user where to save the document, receive a URL, and then create the SQLite database myself.

Is there a SwiftUI API for this on macOS 26 or later? If not, is NSSavePanel still the recommended approach for this case?

Answered by Frameworks Engineer in 896994022

It is possible to write databases with SwiftUI.

The API you want to use is the one you mentioned in the original post: https://developer.apple.com/documentation/swiftui/view/fileexporter(ispresented:document:contenttype:defaultfilename:oncompletion:oncancellation:)

Here's the document implementation that allows to write directly to the URL:

final class MyDocument: WritableDocument {
    static let writableContentTypes: [UTType] = [.database] // or better, declare a custom UTType for your document type
    private var info: MyDatabaseInfo

    func writer(configuration: sending WriteConfiguration) -> sending MyWriter {
        MyWriter()
    }

    @MainActor
    func snapshot(contentType: UTType) async throws -> sending MyDatabaseInfo { info }
}

struct MyDatabaseInfo { }

struct MyWriter: DocumentWriter {
    @concurrent
    func write(
        snapshot: sending MyDatabaseInfo, to destination: sending URL,
        previous: sending MyDatabaseInfo?, progress: consuming Subprogress
    ) async throws {
        // write the database to destination
    }
}

Thanks for the clear write-up. Your reading is correct.

As of macOS 26, SwiftUI's file modifiers are fileImporter (open, returns URLs), fileExporter (writes a document or Transferable item you supply), and fileMover (moves an existing file). None of them return a bare destination URL the way NSSavePanel does, so there is no SwiftUI equivalent for choosing a save location and getting a URL back. Presenting .fileExporter with an empty placeholder document only to read its URL writes an empty file and works against how the modifier operates, so it is not a substitute.

NSSavePanel is the supported way to get that destination URL, and using it from a SwiftUI app is a normal use of AppKit, not a workaround. You present it from your SwiftUI action, read panel.url, and pass that URL to your database. Since you create the file at that location, the sandbox behavior is relevant: the NSSavePanel documentation notes that when someone saves through the panel, macOS adds the chosen file to your app's sandbox, so the URL is writable without additional entitlement work.

If you run into anything specific while wiring the returned URL to your database, I am glad to talk about that.

@DTS Engineer

Thanks for the quick follow-up. I'm pretty familiar with what you've presented. The "problem" with invoking NSSavePanel from within a SwiftUI macOS application is that you don't necessarily have access to the underlying NSWindow to properly present the NSSavePanel as a sheet, which would be the expected user experience.

I'm aware of the following ways to work around this, all of which feel awkward in a pure SwiftUI environment:

  • Assume the application's current key window is the one to use.
  • Use an NSViewRepresentable as a "window accessor"
  • Abandon the full SwiftUI app lifecycle and just using a traditional AppKit lifecycle for window management.

Are there other ways I've overlooked that are more idiomatic SwiftUI?

The ability to ask the user for a URL to save content to is such a fundamental macOS feature that it's omission genuinely perplexes me.

Are you able to elaborate at all as to what the general rational and thinking is behind this?

Thanks for writing back. You may already be doing this, in which case take it as confirmation rather than news: NSSavePanel does not need an NSWindow to present at all.

Calling panel.runModal() from your SwiftUI action presents the panel app-modally and returns the chosen URL, with no window reference involved. That keeps you entirely within the SwiftUI app lifecycle, and it avoids the window question entirely. The tradeoff is that the panel appears as a standalone modal dialog rather than as a sheet attached to your window.

The window only matters if you specifically want that document-modal sheet, through beginSheetModal(for:). That path does need the hosting NSWindow, and as of macOS 26 SwiftUI does not expose a first-class accessor for it. If your app has a single window, NSApp.keyWindow is a perfectly good way to get it. The ambiguity only arises once more than one window can be key; for that case, a small NSViewRepresentable that reads its view's window is the more robust idiom. Either way, dropping the SwiftUI app lifecycle for an AppKit one is not necessary: neither runModal(), NSApp.keyWindow, nor the NSViewRepresentable accessor requires it.

Either path is supported: runModal() for an app-modal panel, or beginSheetModal(for:) for the sheet, getting the window from NSApp.keyWindow (single window) or an NSViewRepresentable (multiple windows).

If a SwiftUI-native way to request a save destination would fit your app better, that is a good thing to send through Feedback Assistant, which puts it directly in front of the team that owns these APIs.

Please let me know if there are any questions.

@DTS Engineer

We used to routinely present dialogs modally before sheets were introduced around 10.6 I believe. The introduction of sheets provided a more refined user experience and were quickly adopted by both document-based applications and shoebox-based applications.

As far as I know (the HIG no longer explicitly calls this out), using sheets for open and save dialogs remains the recommended user experience.

panel.runModal() has its use-cases, like when selecting a document to open after the application has launched, but it doesn't seem like the right fit for many macOS use cases.

If a SwiftUI-native way to request a save destination would fit your app better, that is a good thing to send through Feedback Assistant

Fair enough. Given SwiftUI already has considerable APIs for fileImporter, fileExporter, fileMover and fileDialog, I assumed I was overlooking an existing API for this common macOS use case.

Thanks for the discussion.

@DTS Engineer

Feedback Assistant: FB23515927

It is possible to write databases with SwiftUI.

The API you want to use is the one you mentioned in the original post: https://developer.apple.com/documentation/swiftui/view/fileexporter(ispresented:document:contenttype:defaultfilename:oncompletion:oncancellation:)

Here's the document implementation that allows to write directly to the URL:

final class MyDocument: WritableDocument {
    static let writableContentTypes: [UTType] = [.database] // or better, declare a custom UTType for your document type
    private var info: MyDatabaseInfo

    func writer(configuration: sending WriteConfiguration) -> sending MyWriter {
        MyWriter()
    }

    @MainActor
    func snapshot(contentType: UTType) async throws -> sending MyDatabaseInfo { info }
}

struct MyDatabaseInfo { }

struct MyWriter: DocumentWriter {
    @concurrent
    func write(
        snapshot: sending MyDatabaseInfo, to destination: sending URL,
        previous: sending MyDatabaseInfo?, progress: consuming Subprogress
    ) async throws {
        // write the database to destination
    }
}
SwiftUI on macOS equivalent of NSSavePanel for choosing a destination URL?
 
 
Q