'Creating a macos windowless menu bar application with SwiftUI
I am looking for a solution to create a macos windowless menu bar application with SwiftUI.
I have implement the function ralated to the menu bar, the problem is removing the main window and remove the application from the dock.
I have tried to set Application is agent (UIElement) to YES in Info.plist but it only hide the application from the dock while the window is still there.
And I have tried to modify @main but it doesn't work too.
Is there any way to achieve that? Thank you so much!
My code:
App.swift
import SwiftUI
@main
struct DiskHealthApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem?
var popOver = NSPopover()
func applicationDidFinishLaunching(_ notification: Notification) {
let menuView = ContentView()
popOver.behavior = .transient
popOver.animates = true
popOver.contentViewController = NSViewController()
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let menuButton = statusItem?.button {
menuButton.image = NSImage(systemSymbolName: "externaldrive", accessibilityDescription: nil)
menuButton.action = #selector(menuButtonToggle)
}
}
@objc func menuButtonToggle() {
if let menuButton = statusItem?.button {
self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, world!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Solution 1:[1]
Setting just the Application is agent (UIElement) to YES will not be sufficient. You will also have to change your AppDelegate by adding the following,
- A
NSPopover - Add a
NSStatusItem
into your AppDelegate for things to work
How to make an NSPopover?
- Go to your app delegate. ( If you don't have an
AppDelegate. Create anAppDelegateclass and delegate it into the starting point of your app which will be annotated with@main. Add yourAppDelegateas follows )
@main
struct SomeApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
}
- After doing so, you can begin making your Menubar app, by changing your
Appdelegateto represent the following
class AppDelegate: NSObject, NSApplicationDelegate {
// popover
var popover: NSPopover!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view (i.e. the content).
let contentView = ContentView()
// Create the popover and sets ContentView as the rootView
let popover = NSPopover()
popover.contentSize = NSSize(width: 400, height: 500)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
// Create the status bar item
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
button.image = NSImage(named: "Icon")
button.action = #selector(togglePopover(_:))
}
}
// Toggles popover
@objc func togglePopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button {
if self.popover.isShown {
self.popover.performClose(sender)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
- After doing so you should/can set
Application is agent(UIElement)toYES
Final Step
This section will be broken down into 2 sections, namely 4.1 and 4.2
4.1is for those who usedAppDelegatelife cycle to initialize their project4.2is for those who created a project usingSwiftUIlife cycle.
4.1 - AppDelegate Life cycle
Go to your Main.storyboard and to delete the Window Controller scene If you have the Main.storyboard. This should get rid of the NSWindow that pops up.
4.2 - SwiftUI Life cycle
Here, since you don't have a Storyboard file to delete scenes, at this point your app will launch with NSWindow and NSPopover. To remove the NSWindow that opens, go to your app starting point which is annotated as @main, and make the following changes to the code
@main
struct SomeApp: App {
// Linking a created AppDelegate
@NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
// IMPORTANT
Settings {
AnyView()
}
}
}
For more info, refer this article
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |

