Custom icon for SwiftUI MenuBarExtra

Created
Updated
TagsSwiftSwiftUI

If you tired already the new API for macOS Ventura – MenuBarExtra – you might have stumbled upon the fact that while SF Symbols work just great for the Manu Bar Icon, custom icons aren’t working as expected. There’s no obvious reason for that, looks like SwiftUI is doing some trickery under the hood and this trickery isn’t really adjustable.

But there’s a way of making it work, so let’s dive right in and see what works and what doesn’t!

Let’s start with the code that uses SF Symbols and works just fine:

MenuBarExtra {
    ContentView()
} label: {
    Image(systemName: "bird.fill")
}
.menuBarExtraStyle(.window)

This gives us a nice bird icon in the Mac’s Menu Bar

Now let’s try something obvious that apparently won’t work. We add a custom icon to our Assets and use it instead of SF Symbol:

MenuBarExtra {
    ContentView()
} label: {
    Image("logo")
}
.menuBarExtraStyle(.window)

And BAM! It renders our icon as an enormous monster:

For some reason SwiftUI ignores the fact it’s being used in MenuBarExtra and respects the original size of the image. Even .resizable() modifier wouldn’t help here.

The next logical step would be to import a smaller-sized image. Even thought it’s an SVG and it’s a vector image, somehow size matters. This renders our icon with a proper size but the image quality seems to suffer, everything is pixelated as if we upscaled a very small raster image:

So now finally we get to the real solution and, as it often happens, what fixes SwiftUI better than a good old UIKit or in our case – AppKit! We initialize an NSImage scale it and then use it to initialize a SwiftUI Image. Unfortunately NSImage cannot respect image ratio so we calculate it manually:

MenuBarExtra {
    ContentView()
} label: {
    let image: NSImage = {
        let ratio = $0.size.height / $0.size.width
        $0.size.height = 18
        $0.size.width = 18 / ratio
        return $0
    }(NSImage(named: "logo")!)

    Image(nsImage: image)
}
.menuBarExtraStyle(.window)

With this in place we now have a nice high-quality proper-sized icon in the Menu Bar!

Thanks for reading! Feel free to approach me if you have any questions or suggestions: ☎️Contact