My SwiftUI menu item renders differently in a MenuBarExtra / NSStatusBar

Hi,

I'm working on an app that will mostly live in the menu bar.

I'm trying to make a menu item that looks similar to the Tailscale app's menu:

Note: I'm inspired by how Tailscale's menu is rendered:

I have made a View that shows my avatar, name, and optionally the company I work for:

import SwiftUI

struct MenuWhoAmI: View {
    var username: String
    var binding: String?
    var body: some View {
        HStack {
            AsyncImage(url: URL(string: "https://avatars.githubusercontent.com/u/76716")!){ image in
                image.resizable().scaledToFit()
            } placeholder: {
                ProgressView()
            }
            .clipShape(Circle())
            
            VStack(alignment: .leading) {
                Text(username)
                if let binding = binding {
                    Text("\(binding)").foregroundStyle(.secondary)
                }
            }
        }
    }
}

#Preview {
    VStack(alignment: .leading) {
        MenuWhoAmI(username: "grahamc").padding()
        Divider()
        MenuWhoAmI(username: "grahamc", binding: "DeterminateSystems").padding()
    }.padding()
}

I tried using it in my menu bar:

import SwiftUI

@main
struct DeterminateApp: App {
    var body: some Scene {
        MenuBarExtra("Determinate", image: "MenuIcon") {
            MenuWhoAmI(username: "grahamc")
                
            Button("Two") {}
            Button("Three") {}
            Divider()
            
            Button("Quit") {
                NSApplication.shared.terminate(nil)
            }.keyboardShortcut("q")
        }.menuBarExtraStyle(.menu)
    }
}

and it renders differently:

After reading the forums and documentation, I understood the MenuBarExtra only renders certain elements. I then tried to use an NSStatusBar with an AppDelegate:

import AppKit
import SwiftUI

@main
struct DeterminateApp: App {
    @NSApplicationDelegateAdaptor private var appDelegate: AppDelegate

    var body: some Scene {
        Window("Authentication", id: "login") {}
    }
}

class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
    private var statusItem: NSStatusItem!

    func applicationDidFinishLaunching(_ notification: Notification) {
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        if let button = statusItem.button {
            button.image = NSImage(named: NSImage.Name("MenuIcon"))
         }
        
        statusItem.menu = NSHostingMenu(rootView: Group {
            Button(action: { print("hi") }) {
                MenuWhoAmI(username: "grahamc")
            }
        })
    }
}

and still, the avatar/name doesn't render like I'd expect, missing the circle clipping:

...and I'm a bit mystified.

How can I make this menu render the way I'm trying for?

Thank you!

There is no API for embedding a view with custom drawing behavior as a menu item using the NSHostingMenu or SwiftUI menu APIs.

But, for this case, there’s still something you can do! In order to crop the image to a circle, you’ll need to use an ImageRenderer or possibly CoreImage to apply the clip shape you need and resize to an appropriate size. But once you have that masked image, this setup will allow you to render a title, subtitle, and image in a single menu item:

Button(action: { ... }) {
  // icon
  Image(...)
  // title
  Text("grahamc")
  // subtitle
  Text("DeterminateSystems")
}

The crucial bit is that both the title and subtitle Text views are part of the label of a Button/Menu.

My SwiftUI menu item renders differently in a MenuBarExtra / NSStatusBar
 
 
Q