SwiftUI: Issue with buttons in modal windows (macOS)

Viewed 99

When I trigger a modal window on macOS through a Button that has a ButtonStyle, a Button in that modal that also has a ButtonStyle doesn't register the click until after the modal window is closed. Buttons without styles work fine regardless.

This seems like a SwiftUI bug to me or am I missing something here? I'd like to use NSApp.runModal as it seems to be the only way to block the main window while the modal is open. Example code below video.

enter image description here

Opening modal window based on this code: https://github.com/onmyway133/blog/issues/768

struct ContentView: View {
    
    var modalWindowContent: some View {
        VStack {
            Button("Regular Button") {
                print("Regular Button Pressed")
            }
            Button("Plain Button") {
                print("Plain Button Pressed")
            }
            .buttonStyle(.plain)
        }
        .frame(width: 200, height: 100)
    }
    
    var body: some View {
        Group {
            Button("Open Modal Window") {
                openModalWindow()
            }
            Button("Open Modal Window") {
                openModalWindow()
            }
            .buttonStyle(.plain)
        }
        .frame(width: 300, height: 200)
    }
    
    func openModalWindow() {
        let window = ModalWindow(
            contentRect: .zero,
            styleMask: [.titled, .closable, .resizable],
            backing: .buffered,
            defer: false
        )
        window.titlebarAppearsTransparent = true
        window.title = "Title"
        window.center()
        window.isReleasedWhenClosed = false
        let view = modalWindowContent
        let hosting = NSHostingView(rootView: view)
        window.contentView = hosting
        hosting.autoresizingMask = [.width, .height]
        NSApp.runModal(for: window)
    }
}

final class ModalWindow: NSWindow {
    override func becomeKey() {
        super.becomeKey()
        level = .statusBar
    }
    
    override func close() {
        super.close()
        NSApp.stopModal()
    }
}
1 Answers

Here's the solution that hopefully saves others from spending hours on this like I did. For whatever reason the action of the button opening the window needs to be explicitly dispatched on the main thread:

Button("Open Modal Window") {
    DispatchQueue.main.async {
        openModalWindow()
    }
}
.buttonStyle(.plain)
Related