DEV Community

Cover image for What I Learned Building a Native macOS Menu Bar App
heocoi
heocoi

Posted on

What I Learned Building a Native macOS Menu Bar App

I just shipped VPN Peek - a menu bar utility for macOS — and the journey taught me more about AppKit than I expected.

Here's what I wish someone told me before I started.

NSPopover vs NSMenu: Choose Wisely

Most tutorials show NSPopover for menu bar apps. It's easy — just attach a SwiftUI view and you're done.

But it never felt right. The popover has a slight delay, doesn't dismiss naturally, and looks like a "floating app" rather than a system utility.

I switched to NSMenu with custom views:

let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

let menu = NSMenu()
let customItem = NSMenuItem()
customItem.view = NSHostingView(rootView: YourSwiftUIView())
menu.addItem(customItem)

statusItem.menu = menu
Enter fullscreen mode Exit fullscreen mode

The difference? It feels like a real Mac app now. Instant open, standard dismiss behavior, native animations.

Lesson: If your app lives in the menu bar, make it feel like it belongs there.

The Hybrid Trap

"Just use SwiftUI" is tempting advice. But menu bar apps hit SwiftUI's edge cases fast:

  • Window management is weird
  • Menu styling is limited
  • Some AppKit APIs have no SwiftUI equivalent

I ended up with roughly 70% SwiftUI, 30% AppKit. SwiftUI for views and state, AppKit for system integration.

Don't fight it. Embrace the hybrid.

Sandbox Will Break Your Heart

My original feature list was ambitious. Then I met macOS sandbox.

Things I couldn't do in a Mac App Store app:

  • Access other apps' network connections
  • Read system logs
  • Monitor processes

Half my "cool ideas" died here. But honestly? It forced me to focus. The app does fewer things, but does them well.

Tip: Check sandbox limitations before you design features, not after.

Small Details That Matter

Some things I obsessed over that users actually noticed:

Adaptive menu bar icon. Light icon on dark menu bar, dark icon on light. Sounds obvious, but many apps get this wrong.

statusItem.button?.image = NSImage(named: "MenuIcon")
statusItem.button?.image?.isTemplate = true // This one line handles dark/light mode
Enter fullscreen mode Exit fullscreen mode

Proper number formatting. "1234 ms" vs "1,234 ms" vs "1.2s". Tiny detail, but it signals polish.

No Dock icon. Menu bar apps shouldn't clutter the Dock.

<!-- Info.plist -->
<key>LSUIElement</key>
<true/>
Enter fullscreen mode Exit fullscreen mode

These aren't features. They're expectations. Miss them and your app feels off.

App Store Review: Expect the Unexpected

My first submission was rejected. Reason? The app "didn't have enough functionality."

I added a few quality-of-life features, resubmitted, approved.

No logic change. Just perception.

Lesson: Reviewers spend seconds on your app. Make sure it looks complete at first glance.

What I'd Do Differently

Start with NSMenu from day one. I wasted time on NSPopover before accepting it wasn't right.

Build the preferences window early. I kept hardcoding values during development. Technical debt adds up.

Target macOS 14+ and don't look back. Modern Swift features like @Observable are worth dropping older OS support.

Worth It?

Building for macOS is humbling. The platform has opinions, and fighting them is painful.

But when everything clicks — when your app feels like it belongs on macOS — that's satisfying in a way web development never gave me.

VPN Peek is on the Mac App Store if you want to see the result.

And I'm launching on Product Hunt this Saturday (Jan 10) if you want to support the journey.

Happy to answer questions about menu bar development in the comments 👇

Top comments (0)