I'm creating a menu bar app for macOS using SwiftUI.I have figured out how to create a menu bar item, and how to hide the icon from the Dock. But there is one thing left I need to figure out to have a proper menu bar app, and that is how to not show a window on screen (see screenshot).
When using SwiftUI for the @main App
class, it looks like I have to return a WindowGroup
with some content. I've tried EmptyView()
etc. but it has to be "some view that conforms to Scene
".
Here is the code I have so far.
import SwiftUI
import Combine
class AppViewModel: ObservableObject {
@Published var showPopover = false
}
@main struct WeeksApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup { Text("I don't want to show this window. 🥲") }
.onChange(of: scenePhase) { phase in
switch phase {
case .background:
print("Background")
case .active:
print("Active")
case .inactive:
print("Inactive")
@unknown default:
fatalError()
}
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?
var viewModel = AppViewModel()
var cancellable: AnyCancellable?
override init() {
super.init()
cancellable = viewModel.objectWillChange.sink { [weak self] in
if (self?.viewModel.showPopover == false) {
self?.closePopover(self)
}
}
}
func applicationDidFinishLaunching(_ notification: Notification) {
popover.behavior = .transient
popover.animates = false
popover.contentViewController = NSViewController()
popover.contentViewController?.view = NSHostingView(rootView: ContentView(viewModel: viewModel))
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusBarItem?.button?.title = "Week \(getCurrentWeekNumber())"
statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
}
@objc func showPopover(_ sender: AnyObject?) {
if let button = statusBarItem?.button {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
@objc func closePopover(_ sender: AnyObject?) {
popover.performClose(sender)
}
@objc func togglePopover(_ sender: AnyObject?) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
func getCurrentWeekNumber() -> Int {
let calendar = Calendar.current
let weekOfYear = calendar.component(.weekOfYear, from: Date())
return weekOfYear
}
}
So how can I launch the app without showing a window? I would prefer a SwiftUI solution. I know it can be done the "old" way.
I'm not sure if it's a hack and if there is a better way. I wrote an app as an agent, with the possibility to show a window later in the process. However, at startup, the app should not show a window.
I made an application delegate with the following code:
class Appdelegate: NSObject, NSApplicationDelegate {
@Environment(\.openURL) var openURL
var statusItem: NSStatusItem!
func applicationDidFinishLaunching(_ notification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.button!.image = NSImage(named: "AppIcon")
statusItem.isVisible = true
statusItem.menu = NSMenu()
addConfigrationMenuItem()
BundleLocator().checkExistingLaunchAgent()
if let window = NSApplication.shared.windows.first {
window.close()
}
}
}
Notice at the end I close the window, which let my app start in as a menubar app only. However, I do have a content view that can be opened later. The app therefore looks like this:
@main
struct RestartAtDateTryoutApp: App {
@NSApplicationDelegateAdaptor(Appdelegate.self) var appDelegate
var scheduler = Scheduler()
var body: some Scene {
WindowGroup {
ContentView()
.handlesExternalEvents(preferring: Set(arrayLiteral: "ContentView"), allowing: Set(arrayLiteral: "*"))
.environmentObject(scheduler)
.frame(width: 650, height: 450)
}
.handlesExternalEvents(matching: Set(arrayLiteral: "ContentView"))
}
}
This gives me the menubar app with items of which one is a window with a user interface. I hope this also works in your app.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.