Is there a way not to show a window for a SwiftUI-lifecycle macOS app?

Viewed 1580

I'm looking to not show a window for a SwiftUI application on macOS. The app uses SwiftUI's application lifecycle and only runs in the status bar. Showing a window on start up is unnecessary. I'm unsure however how to get around the WindowGroup. There's no such a thing as an EmptyScene and putting an EmptyView inside the WindowGroup of course creates an empty window.

@main
struct MyApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

I basically only need the app delegate. I'm guessing using the default AppKit lifecycle makes more sense, but if there is a way to use SwiftUI's lifecycle, I'd love to know.

4 Answers

In your AppDelegate, do the following in your applicationDidFinishLaunching:

func applicationDidFinishLaunching(_ notification: Notification) {
    // Close main app window
    if let window = NSApplication.shared.windows.first {
        window.close()
    }
    
    // Code continues here...
}

For example:

class AppDelegate: NSObject, NSApplicationDelegate {
    var popover = NSPopover.init()
    var statusBar: StatusBarController?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        // Close main app window
        if let window = NSApplication.shared.windows.first {
            window.close()
        }
        
        // Create the SwiftUI view that provides the contents
        let contentView = ContentView()

        // Set the SwiftUI's ContentView to the Popover's ContentViewController
        popover.contentSize = NSSize(width: 160, height: 160)
        popover.contentViewController = NSHostingController(rootView: contentView)
        
        // Create the Status Bar Item with the above Popover
        statusBar = StatusBarController.init(popover)
    }
    
}

You should be able to do something like this:

var body: some Scene {
  WindowGroup {
    ZStack {
      EmptyView()
    }
    .hidden()
  }
}

If you app has settings (who doesn't?), you can do like this:

@main
struct TheApp: App {
  @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  var body: some Scene {
    Settings {
      SettingsView()
    }
  }
}

If you need conditional, this approach works for me. I don't like hiding by collapsing frame by couldn't find more reliable.

@main
struct MacApp: App {

    @ObservedObject var rootInteractor = RootInteractor()
    
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: ViewModel())
                .frame(height: rootInteractor.shouldShowWindow ? nil : 0)
        }
    }
}
    
    final class RootInteractor: ObservableObject {
    
        let shouldShowWindow: Bool
    
        init() {
            shouldShowWindow = !CommandLine.arguments.contains("-sendmsg")
        }
    }
Related