1

I have a SwiftUI app that I am creating. Upon the user closing the last window, I would like to prompt the user and inform them that the app will also quit.

I have taken a look at both the solutions for creating an alert upon app quiting here and have also looked at the solution for closing the application when the last window closes here.

Both of which I have gotten to work however, not together. What I am looking for is a way to detect when a user closes the last window in the application, then prompt the user with an alert letting them know it will quit the application and asking if they would like to continue or cancel.

Using .onDisappear does not seem to work. I have implemented a appDelegate and it's applicationShouldTerminateAfterLastWindowClosed method, but when the last window closes, it does not seem to prompt the .alert behavior in my application.

Application class

class Application: NSObject, NSApplicationDelegate, ObservableObject {
    
    @Published var willTerminate = false
    
    override init() {
        super.init()
    }
    
    func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
        if NSApplication.shared.windows.count == 0 {
            return .terminateNow
        }
        self.willTerminate = true
        return .terminateLater
    }
    
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }
    
    func resume() {
        NSApplication.shared.reply(toApplicationShouldTerminate: false)
    }
    
    func close() {
        NSApplication.shared.reply(toApplicationShouldTerminate: true)
    }
}

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) {}
}

ContentView

struct ContentView: View {
    @State private var window: NSWindow?
    @EnvironmentObject private var appDelegate: Application
     var body: some View {
        ZStack {
            MyView()
            // ...
            .onDisappear(
                // Code in here does not run when WindowAccessor is set to background
                })
                .background(WindowAccessor(window: self.$window))
                .alert(isPresented: Binding<Bool>(get: { self.appDelegate.willTerminate && self.window?.isKeyWindow ?? false }, set: { self.appDelegate.willTerminate = $0 }), content: {
                    SoloLogger(for: .window).coreLog(message: "ApplicationClosedEvent", level: .info)
                    return Alert(title: Text("Quit Application?"),
                          message: Text("Do you really want to quit the application?"),
                          primaryButton: .default(Text("Cancel"), action: {self.appDelegate.resume() }),
                          secondaryButton: .destructive(Text("Quit"), action: {self.appDelegate.close()}))
                })
        }
    }
}
Jav Solo
  • 576
  • 1
  • 6
  • 15

1 Answers1

0

I've been working on something similar.

You can pick up the @AppDelegate from the environment and don't need to create a WindowAccessor.

I created a view which can be added into your content view's ZStack:

struct MacOSQuitCheckView: View {
    
    // MARK: - PROPERTIES
    @EnvironmentObject private var appDelegate: AppDelegate
    
    // MARK: - VIEW BODY
    var body: some View {
        EmptyView()
            .alert("App wants to quit?"), isPresented: isPresented) {
                Button("Do not quit", role: .cancel, action: appDelegate.resume)
                Button("Quit", action: appDelegate.close)
            }
    }
    
    // MARK: - PRIVATE COMPUTED PROPERTIES
    private var isPresented: Binding<Bool> {
        Binding<Bool>(get: { self.appDelegate.willTerminate }, set: { self.appDelegate.willTerminate = $0 })
    }
}
Philip Pegden
  • 1,732
  • 1
  • 14
  • 33