A full-powered menu-bar app that beautifully blends SwiftUI and AppKit
Features • Installation • Architecture • Resources
Font Switch is a full-powered menu-bar application that pushes SwiftUI to its limits and uses AppKit to reach the finish line. While MenuBarExtra works great for simple cases, it doesn't handle right-click detection or draggable windows. SwiftUI's new window-management APIs are powerful, but not quite complete—for example, there's no way to programmatically set the key (focused) window.
Font Switch showcases how to build a production-ready menu-bar application using Swift 6 and the latest and greatest from SwiftUI—including the @Observable macro, window APIs, and onKeyPress() modifier. It also demonstrates Sparkle 2.0 integration for App Store alternatives and global shortcuts via HotKey. And that's just the tip of the iceberg.
- 🎯 Global Font Switching — Instantly change the font of text selected in any application
- 📚 Full-Fledged Font Manager — CRUD operations for fonts and font collections
- 👁️ Font Visibility Control — Hide unwanted fonts to declutter your font picker
- ⌨️ Keyboard Navigation — Full keyboard support with focus rings and arrow key navigation
- Modern Architectures — Swift 6, structured concurrency, and the
@Observablemacro - SwiftUI + AppKit Integration — Full-powered menu-bar app using
NSStatusItem,NSPanel, and@NSApplicationDelegateAdaptor - Keyboard Shortcuts Galore — Global shortcuts via HotKey and view-specific commands via
onKeyPress() - Custom Environment Actions —
DismissAction-style custom environment types - Sparkle Integration — Complete app update system with beta channel support via Sparkle
Floating font-picker panel with programmatic focus (key state)
Font manager with collection browser and font visibility controls
Settings and menu-bar right-click menu
- macOS 15.0 or later
- Xcode 26.0 or later
- Swift 6.0 or later
Before building, set:
DEVELOPMENT_TEAM— your Apple Team IDBUNDLE_ID_PREFIX— your reverse-domain prefix (e.g.com.example)
Open the project in Xcode 26 or later and run on "My Mac".
To set up Sparkle, check out Matteo Spada's exceptional implementation guide.
Here's how Font Switch tackles some interesting macOS development challenges:
Since MenuBarExtra has limitations, Font Switch uses NSStatusItem for the menu bar integration:
- Full control over right-click detection and context menus
NSHostingMenuto power AppKit menus with SwiftUI views—handy for avoiding runtime warnings when opening settings outside of aView
The floating font picker uses a custom NSPanel implementation where standard window APIs fall short:
FocusablePanelsubclass handles proper focus management and position persistence across app launches- Control whether the panel receives keyboard input (for searching) or the app with selected text receives input (for applying fonts) via the
@Environment
Font Switch implements custom environment actions that work like DismissAction:
@Environment(\.focusMainWindow) private var focusMainWindow
Button("Focus Window") {
focusMainWindow()
}Font collection management uses NSFontManager for the heavy lifting:
- Create, rename, and delete collections
- Add fonts via context menu or drag-and-drop using the
Transferableprotocol - Font visibility control with
UserDefaultspersistence - Rich text formatting preservation when switching fonts across applications
Keyboard navigation combines multiple techniques:
onKeyPress()for view-specific commands@FocusStatefor managing focus across the interface- Custom focus rings that match each view's shape for a polished feel
The font switching works by manipulating the pasteboard:
- RTF formatting preservation
- Programmatic copy/paste with
CGEventsimulation - Original pasteboard state restoration after operations
- SwiftUI Mac Windows — Overview of new window APIs
NSFontManagerIdiosyncrasies — API tips and tricks- Making
NSPanelKeyable — Making a utility window keyable (focusable) - Sparkle 2.0 — Exceptional tutorial from Matteo Spada
- Requesting Accessibility Access — How to interact with other apps
Font Switch is available under the MIT License. See LICENSE file for details.