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.swiftimport 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.swiftimport SwiftUI
struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
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,
NSPopoverNSStatusIteminto your AppDelegate for things to work
AppDelegate. Create an AppDelegate class and delegate it into the starting point of your app which will be annotated with @main. Add your AppDelegate as follows )@main
struct SomeApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
}
Appdelegate to represent the followingclass 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)
            }
        }
    }
    
}
Application is agent(UIElement) to YESThis section will be broken down into 2 sections, namely 4.1 and 4.2
4.1 is for those who used AppDelegate life cycle to initialize their project4.2 is for those who created a project using SwiftUI 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.
 (image credit)
(image credit)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With