Lupurus
Lupurus

Reputation: 4189

SwiftUI and AppKit: Use close dialog to ask if the app is allowed to quit

I am using Big Sur and SwiftUI with the SwiftUI lifecycle. I want to implement an alert, where the user gets asked, if the application can be quit or not. How is this possible with SwiftUI? It should look like this:

enter image description here

Upvotes: 4

Views: 1836

Answers (1)

Lupurus
Lupurus

Reputation: 4189

It's possible by using this code (this code opens the Alert only in the key window):

import SwiftUI
import AppKit

class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
    
    @Published var willTerminate = false
    
    func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
        // check, if at least one window is open:
        if NSApplication.shared.windows.count == 0 {
            // if no one is open, close it
            return .terminateNow
        }
        // if one or more are open, set the willTerminate variable, so that the alert can be shown
        self.willTerminate = true
        // return a .terminateLater (to which we need to reply later!)
        return .terminateLater
    }
    
    /// This method tells the application, that it should not close
    func `continue`() {
        NSApplication.shared.reply(toApplicationShouldTerminate: false)
    }
    /// This method closes the application
    func close() {
        NSApplication.shared.reply(toApplicationShouldTerminate: true)
    }
    
}

@main
struct WindowShouldCloseApp: App {

    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

And this is the ContentView.swift:

import SwiftUI

struct ContentView: View {
    
    @EnvironmentObject private var appDelegate: AppDelegate
    @State private var window: NSWindow?
    
    var body: some View {
        Text("Hello, world!")
            .padding()
            .background(WindowAccessor(window: self.$window))   // access the window
            .alert(isPresented: Binding<Bool>(get: { self.appDelegate.willTerminate && self.window?.isKeyWindow ?? false }, set: { self.appDelegate.willTerminate = $0 }), content: {
                // show an alert, if the application should be closed
                Alert(title: Text("Really close?"),
                      message: Text("Do you really want to close the application?"),
                      primaryButton: .default(Text("Continue"), action: { self.appDelegate.continue() }),
                      secondaryButton: .destructive(Text("Close"), action: { self.appDelegate.close() }))
            })
    }
}

// thanks to Asperi: https://stackoverflow.com/questions/63432700/how-to-access-nswindow-from-main-app-using-only-swiftui/63439982#63439982
struct WindowAccessor: NSViewRepresentable {
    
    @Binding var window: NSWindow?
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async {
            self.window = view.window
        }
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {}
}

Upvotes: 4

Related Questions