macosbuttonswiftuiwindownswindow

Button to open view in new window SwiftUI 5.3 for Mac OS X


I would like to have a button to open a new window and load a view (for the app's Preferences) in SwiftUI for MacOS but am unsure of the correct way to do it.

I tried creating a function and calling it from the button action which works but on closing the new window the app crashes with:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

This is my function:

func openPreferencesWindow() {
    var preferencesWindow: NSWindow!
    let preferencesView = PreferencesView()
    // Create the preferences window and set content
    preferencesWindow = NSWindow(
        contentRect: NSRect(x: 20, y: 20, width: 480, height: 300),
        styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
        backing: .buffered,
        defer: false)
    preferencesWindow.center()
    preferencesWindow.setFrameAutosaveName("Preferences")
    preferencesWindow.contentView = NSHostingView(rootView: preferencesView)
    preferencesWindow.makeKeyAndOrderFront(nil)
}

And this is my button calling it:

Button(action: {
    openPreferencesWindow()
}) {
    Text("Preferences").font(.largeTitle).foregroundColor(.primary)
}

I feel like the window should be constructed in AppDelegate but I'm not sure how I would then call it.


Solution

  • You need to keep reference to created preferences window (like to the main window).

    Here is possible solution (tested with Xcode 11.4 / macOS 10.15.5)

    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
    
        var window: NSWindow!
        var preferencesWindow: NSWindow!    // << here
    
        @objc func openPreferencesWindow() {
            if nil == preferencesWindow {      // create once !!
                let preferencesView = PreferencesView()
                // Create the preferences window and set content
                preferencesWindow = NSWindow(
                    contentRect: NSRect(x: 20, y: 20, width: 480, height: 300),
                    styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
                    backing: .buffered,
                    defer: false)
                preferencesWindow.center()
                preferencesWindow.setFrameAutosaveName("Preferences")
                preferencesWindow.isReleasedWhenClosed = false
                preferencesWindow.contentView = NSHostingView(rootView: preferencesView)
            }
            preferencesWindow.makeKeyAndOrderFront(nil)
        }
    
        // ... other code
    

    and now button would look like

    Button(action: {
        NSApp.sendAction(#selector(AppDelegate.openPreferencesWindow), to: nil, from:nil)
    }) {
        Text("Preferences").font(.largeTitle).foregroundColor(.primary)
    }