Kilian
Kilian

Reputation: 2282

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

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.

Upvotes: 22

Views: 6466

Answers (5)

marc-medley
marc-medley

Reputation: 9832

Since this a status bar menu application …

The app uses SwiftUI's application lifecycle and only runs in the status bar.

… use a MenuBarExtra scene to render a persistent control in the system menu bar with a SwiftUI lifecycle. (macOS 13.0+)

@main
struct MyStatubarMenuApp: App {
    var body: some Scene {
        // -- no WindowGroup required --
        // -- no AppDelegate needed --
        MenuBarExtra { 
            Text("Hello Status Bar Menu!")
            MyCustomSubmenu()
            Divider()
            Button("Quit") { NSApp.terminate(nil) }
        } label: {
            Image(systemName: "bolt.fill")
        }
    }
}

Notes:

  • Add and set Application is agent = YES in Info.plist for the app icon to not show on the dock.
  • systemName: SF Symbols will automatically toggle appearance when the System Settings… > Appearance is changed.
  • @Environment(\.colorScheme) does not currently update at the App/Scene level

Upvotes: 11

webcpu
webcpu

Reputation: 3422

var body: some Scene {
    WindowGroup{
        EmptyView()
            .frame(width: 0, height: 0)
            .hidden()
    }
    .windowResizability(.contentSize)
}

the windowResizability(_:) is available on macOS 13+

Upvotes: 0

Aliaksandr Bialiauski
Aliaksandr Bialiauski

Reputation: 1590

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

Upvotes: 10

mrtom
mrtom

Reputation: 2156

You should be able to do something like this:

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

Upvotes: 6

Jesus Fidalgo
Jesus Fidalgo

Reputation: 251

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

Upvotes: 25

Related Questions