🤖 Auto-generated by weekly-pattern-learner · SwiftUI macOS menubar app pattern observed in FocusNotch session, Jun 2026 (364 user turns, ~48 MB transcript covering idea → spec → impl → critique cycles → App Store)
macOS Menubar / Notch App (SwiftUI)
Overview
Build a macOS app that lives in the menu bar or notch using SwiftUI's MenuBarExtra. Covers the full lifecycle: project setup, window management, entitlements, user interaction (keyboard shortcuts, sound), and App Store preparation.
When to Use
- User asks to build an app for the macOS menubar, status bar, or notch
- Adding a menubar presence to an existing macOS app
- Debugging sandbox rejections or entitlement errors in a SwiftUI app
Workflow
1. Project Setup
Create a new macOS App target in Xcode:
- Interface: SwiftUI
- App Sandbox: Enabled (required for App Store)
- Bundle ID:
com.yourname.AppName - Deployment target: macOS 13+ (for
MenuBarExtra)
In Info.plist, set:
<key>LSUIElement</key>
<true/>
This hides the app from the Dock: the menu bar becomes the only entry point.
2. MenuBarExtra (SwiftUI native)
@main
struct FocusApp: App {
var body: some Scene {
MenuBarExtra {
ContentView()
.frame(width: 320, height: 480)
} label: {
Label("Focus", systemImage: "timer")
}
.menuBarExtraStyle(.window)
}
}
Use .menuBarExtraStyle(.window) for a popover-style panel. Use .menuBarExtraStyle(.menu) for a dropdown menu.
3. Notch Detection
On MacBooks with a notch, the safe area changes. Detect and adapt:
func isNotchPresent() -> Bool {
guard let screen = NSScreen.main else { return false }
// The notch area is excluded from visibleFrame top
let screenHeight = screen.frame.height
let visibleTop = screen.visibleFrame.maxY
return visibleTop < screenHeight
}
Position windows below the notch:
window.setFrameOrigin(NSPoint(
x: window.frame.origin.x,
y: NSScreen.main!.frame.height - window.frame.height - notchOffset
))
4. Sandbox Entitlements
Minimum .entitlements for a sandboxed menubar app:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add only what the app actually uses: -->
<!-- <key>com.apple.security.network.client</key> -->
<!-- <true/> -->
</dict>
</plist>
For focus/blocking features that need accessibility:
<key>com.apple.security.temporary-exception.apple-events</key>
<array>
<string>com.apple.systemevents</string>
</array>
5. Accessibility Permission (Focus Detection)
To detect which app is in focus (required for distraction blocking):
import ApplicationServices
func requestAccessibilityPermission() -> Bool {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
return AXIsProcessTrustedWithOptions(options)
}
Call this on first launch and prompt the user to grant access in System Settings → Privacy → Accessibility.
6. Sound Effects
import AVFoundation
class SoundPlayer {
private var player: AVAudioPlayer?
func play(_ filename: String, extension ext: String = "mp3") {
guard let url = Bundle.main.url(forResource: filename, withExtension: ext) else { return }
player = try? AVAudioPlayer(contentsOf: url)
player?.play()
}
}
Add sound files to the Xcode target (check Target Membership in file inspector).
For system sounds (no file needed):
NSSound.beep()
// or
NSSound(named: .init("Hero"))?.play()
7. Keyboard Shortcuts
Global shortcuts (work when app is not focused); requires Accessibility permission:
NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { event in
if event.modifierFlags.contains([.command, .shift]) && event.keyCode == 36 { // ⌘⇧↩
// Handle shortcut
}
}
Local shortcuts (SwiftUI):
.keyboardShortcut("s", modifiers: [.command, .shift])
8. Persistent State (AppStorage)
@AppStorage("timerDuration") private var timerDuration: Double = 25 * 60
@AppStorage("soundEnabled") private var soundEnabled: Bool = true
For complex state, use UserDefaults with a suite name to share across extensions.
9. Polish Cycle
After each implementation slice:
Optional skills: Steps 1 and 3 require
/critiqueand/whimsy-injectorfrom the agent-skills catalog. Install them viaskillshareif not already available; skip these steps if not installed.
- Run
/critique: evaluate against Apple HIG, WCAG contrast, interaction consistency - Fix all P0/P1 findings
- Run
/whimsy-injectoron completion screens and idle states - Test on a machine without a notch to verify layout
10. App Store Preparation
See olko:apple-store-submit for the full submission and rejection-handling workflow.
Pre-submission checklist:
# Validate archive
xcodebuild archive -scheme <AppName> -archivePath build/<AppName>.xcarchive
# Check sandbox
codesign --verify --deep --strict build/<AppName>.xcarchive/Products/Applications/<AppName>.app
# Verify entitlements
codesign -d --entitlements :- build/<AppName>.xcarchive/Products/Applications/<AppName>.app
Key Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| App appears in Dock | LSUIElement missing |
Add to Info.plist |
| Menubar icon missing after build | Asset catalog misconfigured | Use PDF or SVG template image, tick Template Image |
| Crash on click | View not loaded in popover window | Add .frame() to MenuBarExtra content |
| Sound not playing | File not in target | Check Target Membership in file inspector |
| Accessibility prompt not shown | Missing entitlement | Add temporary-exception.apple-events or use XPC |
| Notch overlap | Hardcoded y-position | Detect visibleFrame vs frame delta |
Verification
- App does not appear in Dock (
LSUIElement= true) - MenuBarExtra icon renders as template image (respects dark/light mode)
- Sound plays on timer events
- Keyboard shortcuts respond both globally and locally
- App passes
codesign --verify --deep --strict - Layout correct on M1 MacBook (no notch) and M3 MacBook Pro (notch)