Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create status bar icon & menu with SwiftUI like in macos Big Sur

What is SwiftUI API for creating status bar menus?

Apple seems to use SwiftUI views in Battery & WiFi menus according to the accessibility inspector. Screenshot of a battery menu attached, also its view hierarchy.

Battery menu screenshot View hierarchy of the battery menu

EDIT:

Posted the solution as a separate answer.

like image 947
Ruzard Avatar asked Nov 22 '20 00:11

Ruzard


3 Answers

Since this question received more attention lately, and the only reply doesn't fully solve the issue I would like to repeat the edited part of my question and mark it as resolved.

Found a way to show this in swiftui without an annoying NSPopover. You'd need to have AppDelegate or NSApplicationDelegateAdaptor in case you use SwiftUI App lifecycle. Then you create NSMenu and add NSMenuItem that can have a custom view.

Here is the code:

let contentView = VStack {
    Text("Test Text")
    Spacer()
    HStack {
        Text("Test Text")
        Text("Test Text")
    }
    Spacer()
    Text("Test Text")
}

let view = NSHostingView(rootView: contentView)

// Don't forget to set the frame, otherwise it won't be shown.
view.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
        
let menuItem = NSMenuItem()
menuItem.view = view
        
let menu = NSMenu()
menu.addItem(menuItem)
        
// StatusItem is stored as a class property.
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
self.statusItem?.menu = menu
self.statusItem?.button?.title = "Test"

enter image description here

like image 78
Ruzard Avatar answered Oct 13 '22 00:10

Ruzard


Inside the AppDelegate add the following code:

// Create the status item in the Menu bar 
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))

// Add a menu and a menu item
let menu = NSMenu()
let editMenuItem = NSMenuItem()
editMenuItem.title = "Edit"
menu.addItem(editMenuItem)

//Set the menu 
self.statusBarItem.menu = menu

//This is the button which appears in the Status bar
if let button = self.statusBarItem.button {
    button.title = "Here"
}

This will add a Button with a custom Menu to your MenuBar.

enter image description here

Edit - How to use SwiftUI View

As you asked, here is the answer how to use a SwiftUI View.

First create a NSPopover and then wrap your SwiftUI view inside a NSHostingController.

var popover: NSPopover


let popover = NSPopover()
popover.contentSize = NSSize(width: 350, height: 350)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
    self.popover = popover

Then instead of showing a NSMenu, toggle the popover:

self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
     button.title = "Click"
     button.action = #selector(showPopover(_:))
}

With following action:

@objc func showPopover(_ 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)
        }
    }
}

enter image description here

like image 34
davidev Avatar answered Oct 12 '22 22:10

davidev


MenuBarExtra

In macOS 13.0+ and Xcode 14.0+, the MenuBarExtra struct allows you create a system menu bar, that is similar to NSStatusBar's icons and menus. MenuBarExtra's item will be displayed in the system menu bar when the specified binding is set to true.

enter image description here

import SwiftUI

@available(macOS 13.0, *)                       // macOS Ventura
@main struct StatusBarApp: App {
    
    @State private var command: String = "A"
       
    var body: some Scene {

        MenuBarExtra(command, systemImage: "\(command).circle") {
           
            Button("Uno") { command = "A" }
                .keyboardShortcut("U")
           
            Button("Dos") { command = "B" }
                .keyboardShortcut("D")
           
            Divider()

            Button("Salir") { NSApplication.shared.terminate(nil) }
                .keyboardShortcut("S")
        }
    }
}

Getting rid of the App's icon from the Dock

In Xcode's Info tab, choose Application is agent (UIElement) and set its value to YES.

enter image description here

like image 1
Andy Jazz Avatar answered Oct 12 '22 23:10

Andy Jazz