SwiftUI: Run code when window closed macOS

Viewed 479

I have opened my window in the traditional way, but I want to run some code when the window is closed by clicking the close box (red button). Is there a good way of doing this?

func openMyWindow()
{
    myWindow = (NSWindow(
    contentRect: NSRect(x: 100, y: 100, width: 100, height: 600),
    styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
        backing: .buffered, defer: false))
    myWindow!.contentView = NSHostingView(rootView: MyWindowView())
        myWindow!.makeKeyAndOrderFront(nil)
}
2 Answers

Great question.. I struggled on that a time ago as well.

You can make your AppDelegate or class conform to NSWindowDelegate protocol.

Then pass self as the delegate of the window

myWindow.delegate = self

And implement following function to be notified by the closing action

func windowShouldClose(_ sender: NSWindow) -> Bool
{}

Edit:

When you are using SwiftUI life cycle, you can add an AppDelegate. You can also implement your own WindowManager class. Here is an example:

class WindowManager : NSObject, NSWindowDelegate {
    var popUpWindow : NSWindow? = nil
    
    override init() {}
    
    func openMyWindow()
    {
        popUpWindow = (NSWindow(
        contentRect: NSRect(x: 100, y: 100, width: 100, height: 600),
        styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false))
        popUpWindow!.contentView = NSHostingView(rootView: PopUpView())
        popUpWindow!.makeKeyAndOrderFront(nil)
        popUpWindow?.delegate = self
    }
    
    func windowShouldClose(_ sender: NSWindow) -> Bool
    {
        print("Window will close")
        return true
    }
}

struct PopUpView : View {
    var body: some View {
        Text("This is a new popup view")
    }
}

Create and hold that class in your struct _: App and open Windows via this Manager.

@main
struct macSwiftUICycleApp: App {
    let persistenceController = PersistenceController.shared

    let windowManager : WindowManager = WindowManager() //<< Here keep the instance of your WindowManager
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
                .onAppear {
                    windowManager.openMyWindow() //<< Just a test to open another window on startup
                }
        }
    }
}

If the code you want to run is independent of context then the simplest solution is to replace close action for window and run your code there, like

func openMyWindow()
{
    // ... other your code

    myWindow!.contentView = NSHostingView(rootView: MyWindowView())

    let closeButton = myWindow!.standardWindowButton(.closeButton)  // << !!
    closeButton?.action = #selector(NSWindow.doMyClose(_:))           // << !!

    myWindow!.makeKeyAndOrderFront(nil)
}

extension NSWindow {
    @objc
    func doMyClose(_ sender: Any?) {
        // ... run your code here

        self.close()  // << make standard window close

        // ... or here 
    }
}

if you want to execute some closure with captured variables, then the approach is the same but with custom handler class which would hold window and closure to perform.

Related