'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 anAppDelegate
class and delegate it into the starting point of your app which will be annotated with@main
. Add yourAppDelegate
as follows )
@main
struct SomeApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
}
- After doing so, you can begin making your Menubar app, by changing your
Appdelegate
to 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.1
is for those who usedAppDelegate
life cycle to initialize their project4.2
is for those who created a project usingSwiftUI
life 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 |