By default, when you create a Mac application, it is endowed with certain characteristics. One of those is that your application will remain active even when you close all the open windows. The justification is that you may want to create a new window or open an existing data source, so you need a way to do that once all windows have been closed. However, depending on how your application is intended to work, this can equally be frustrating for users who, when closing the last window, may expect the application to close too.
SwiftUI does not provide any reliable way to alter the behaviour of a Mac application. AppKit, however, has a very simple way to control it, so we need to delve into the murky world of AppKit to control how our application works.
Window Delegate
AppKit applications have a delegate for the app that gives the application a way to override a lot of the default functionality of the application. One of those overrides is to a function called applicationShouldTerminateAfterLastWindowClosed which is called to determine the behaviour of the application. It returns a boolean value.
Getting to this function from SwiftUI requires a little work.
The first thing we are going to need is an app delegate class.
import AppKit
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
@AppStorage("closeAppWhenLastWindowCloses") var closeAppWhenLastWindowCloses: Bool = false
/// The user can elect to close the app when the last data window closes or to leave the
/// app open so they can open another file.
///
/// - Returns: True if we should close the app else false.
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return closeAppWhenLastWindowCloses
}
}
It has to be a class because it's interfacing with AppKit and it has to implement the NSApplicationDelegate protocol to define the functions that can be overridden.
Because this is a fundamental behaviour for our app, the current behaviour is held in AppStorage, which we default to false to match the current default behaviour of a Mac application.
When the last window is closed, the applicationShouldTerminateAfterLastWindowClosed function will be called to determine whether the application should close.
Connecting the AppDelegate
Now we have an app delegate, we need to connect our application to it. Just defining the class isn't enough. We can make the connection via the app:
import SwiftUI
@main
struct exitOnCloseAppApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@NSApplicationDelegateAdapter is responsible for making the connection. Once defined, we can start overriding functions in our app delegate.
Testing
If you run the app now, it will perform as it always did. You can open as many windows as you like and the app will remain open when the last one is closed. In order to test it, we need to modify the content view to give us a toggle.
struct ContentView: View {
@AppStorage("closeAppWhenLastWindowCloses") var closeAppWhenLastWindowCloses: Bool = false
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
.padding(.bottom, 40)
Toggle(isOn: $closeAppWhenLastWindowCloses, label: {
Text("Close application when the last window closes")
})
}
.padding()
}
}
I have added the @AppStorage key we used in the app delegate and connected it to a Toggle. By default, when the app is first run, the toggle will be off. If you close the window at this point, the app will remain running. Open a new window and tick the option, then close the window again. This time, the application will close.
Get The Code