Swift and SwiftUI Products Help Contact

SwiftUI macOS Sheet Open/Save panel

In all of the examples I've seen of pragmatically displaying an open or save panel, the panel was displayed as modal dialog freezing the app until the dialog is taken care of.

Yet with AppKit apps, the open and save panel are typically displayed as a sheet attached to the corresponding window. This can be done with SwiftUI, I'll demo two solutions.

Ventura and Xcode 15.2. Published: June 9th 2025

Grab the focused window

The simplest solution is to grab the currently focused window using NSApp.keyWindow and call panel.beginSheetModal passing in the result.

func useOpenToAddSource() { let panel = NSOpenPanel() panel.allowsMultipleSelection = false panel.canChooseDirectories = true panel.beginSheetModal(for: NSApp.keyWindow!) { response in if response == .OK, let url = panel.url { // Do something with the url. } } }

This is not what I'd describe as a concrete solution as it's possible that the focused window isn't the one calling the code. If that's the case then you'll want to use the solution below.

A more concrete solution

This solution is more complicated, but it should ensure that the window we pass to the panel.begingSheetModal function is the one from the calling method, and not just the one with focus.

We do this by using a NSViewRepresentable and overriding the viewDidMoveToWindow instance function of that NSView, to grab an instance of the window and store it in a SwiftUI property (so it can be used with SwiftUI code).

First create a new Swift file in Xcode, and name it "NSWindowGrabber". Then copy the code from below and paste it into the new file.

import SwiftUI public class NSWindowGrabber: NSView { let executeBlock: (_ window: NSWindow? ) -> () init(executeBlock: @escaping (_: NSWindow?) -> Void) { self.executeBlock = executeBlock super.init(frame: NSRect() ) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } public override func viewDidMoveToWindow() { super.viewDidMoveToWindow() executeBlock( self.window ) } } public struct WindowGrabber: NSViewRepresentable { var execute: (_ window: NSWindow? ) -> () public func makeNSView(context: Context) -> some NSView { return NSWindowGrabber(executeBlock: execute) } public func updateNSView(_ nsView: NSViewType, context: Context) { } }

With the supporting file in place, we can now add our view to the SwiftUI view and utilize it to grab a reference to the Window. First lets create a local property to store the window. In the opening lines of the view struct, add the following.

@State private var window: NSWindow?

Add the WindowGrabber as a modifier to one of the views.

.background(WindowGrabber { window in self.window = window })

Now that we have our property, and the property is being populated with the window of the view, we can use it when displaying an open or save panel.

func importText() { let panel = NSOpenPanel() panel.canChooseDirectories = false guard let window = window else { print( "Failed to get the window" ) return } panel.beginSheetModal( for: window ) { response in if response == .OK, let url = panel.url { // --- Do something with the URL. } } }

As you can see I've chosen to handle the Window being NIL, in all sense we shouldn't have to as it should only be nil when the view is being removed from display. But it never hurts to handle things like this, just in case.

  • Sleep Aid, our Mac Sleep troublshooter See what your Mac does when it should be asleep - Sleep Aid
  • Ohanaware © 2007 - 2025 Ohanaware Co., Ltd. Registered in Taiwan R.O.C. 🇹🇼
    Site managed by Strawberry Software's Lifeboat - running on DigitalOcean's platform.

    Pages

    Products Contact Weblog SwiftUI Promos

    Company

    About Us Environment Privacy Terms Update Plans

    Connect

    Bluesky Facebook Threads X / Twitter Mailing List