← Back to all projects

LEARN MACOS STATUS BAR APPS

Learn macOS Status Bar Apps: From Zero to Menu Bar Master

Goal: Deeply understand how to build macOS status bar (menu bar) applications using modern Swift and SwiftUI, from creating a simple icon to managing complex popover UIs and system interactions.


Why Build a Status Bar App?

Status bar apps are a cornerstone of the macOS experience. They are lightweight, always accessible, and perfect for utilities that need to run in the background or provide quick access to information. From system monitoring tools to clipboard managers and mini-music players, the menu bar is prime real estate for powerful applications.

After completing these projects, you will:

  • Master the AppKit components that control status bar items (NSStatusItem).
  • Build modern, interactive UIs using SwiftUI inside popovers.
  • Manage application state and perform background tasks.
  • Interact with the macOS system for things like launching apps and managing user preferences.
  • Know how to package and configure your app to launch at login.

Core Concept Analysis

The Status Bar App Architecture

A macOS status bar application lives outside the main application window model. Its lifecycle and UI are managed by a few key AppKit objects, even when the views themselves are built with SwiftUI.

┌────────────────────────────┐
│        AppDelegate         │
│(Application's Entry Point) │
└─────────────┬──────────────┘
              │ 1. applicationDidFinishLaunching()
              ▼
┌────────────────────────────┐
│       NSStatusBar          │
│ (The System-Wide Menu Bar) │
└─────────────┬──────────────┘
              │ 2. statusBar.statusItem(withLength:)
              ▼
┌────────────────────────────┐
│       NSStatusItem         │
│    (Your App's Icon)       │
└─────────────┬──────────────┘
              │ 3. Set properties:
              │    - button.title
              │    - button.image
              │
    ┌─────────┴──────────┐
    │ 4. Attach a target │
    │    (click action)  │
    ▼                    ▼
┌──────────┐      ┌───────────────────┐
│  NSMenu  │      │     NSPopover     │
│ (Simple  │      │ (Complex UI Host) │
│ Dropdown)│      └─────────┬─────────┘
└──────────┘                │ 5. Set content
                            ▼
                          ┌───────────────────┐
                          │  SwiftUI View     │
                          │(Your Custom Panel)│
                          └───────────────────┘

Key Concepts Explained

  1. NSApplicationDelegate: The entry point for your application. In applicationDidFinishLaunching(_:), you will create and configure your status bar item. Because a status bar app has no main window, you often need to remove the default window from the project’s Storyboard or Info.plist.

  2. NSStatusBar: The system-wide object that owns the status bar. You don’t create this; you access the system’s shared instance via NSStatusBar.system.

  3. NSStatusItem: The object that represents your app’s presence in the status bar. You create it from the NSStatusBar. Its .button property is what you configure with text, an icon, and an action.

  4. NSMenu vs. NSPopover: These are the two primary ways to present a UI.
    • NSMenu: Simple, text-based dropdown menu. Easy to set up and ideal for a list of commands (e.g., “Preferences”, “About”, “Quit”).
    • NSPopover: A floating window that “points” to your status item. It can host any custom view, making it perfect for rich, interactive UIs built with SwiftUI. This is the modern standard.
  5. SwiftUI Integration: To use a SwiftUI view in an AppKit popover, you wrap it in an NSHostingController. This acts as a bridge between the two frameworks.

Project List

These projects are designed to build your skills progressively, starting with the absolute basics and ending with a polished, feature-complete utility.


Project 1: “Hello, Menu Bar”

  • File: LEARN_MACOS_STATUS_BAR_APPS.md
  • Main Programming Language: Swift
  • Alternative Programming Languages: Objective-C (for historical context)
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: macOS AppKit / Application Lifecycle
  • Software or Tool: Xcode
  • Main Book: “Pro Swift” by Paul Hudson (for general Swift concepts).

What you’ll build: The simplest possible status bar app. It will display the text “Hello” in the menu bar. Clicking it will show a menu with a single “Quit” option.

Why it teaches the fundamentals: This project forces you to understand the absolute minimum setup required for a status bar app. You’ll learn how to get an icon on the screen and how to manage the app’s lifecycle without a main window.

Core challenges you’ll face:

  • Configuring a windowless app → maps to removing the default NSWindow from a new macOS project.
  • Accessing the NSStatusBar → maps to getting the system’s status bar instance.
  • Creating an NSStatusItem → maps to the core object that represents your app in the menu bar.
  • Building a simple NSMenu → maps to programmatically creating menu items and linking them to actions.

Key Concepts:

  • Application Delegate: Apple Developer Documentation - NSApplicationDelegate.
  • NSStatusItem: Apple Developer Documentation.
  • NSMenu: “Cocoa Programming for macOS” by Matt Neuburg, Chapter 13.

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic Swift knowledge and familiarity with Xcode.

Real world outcome: Your menu bar will show a new item. When clicked, it displays a functional menu.

                  [] [File] [Edit] ... [Wi-Fi] [Hello] [Date]
                                                 └─ [Quit]

Implementation Hints:

  1. Create a new macOS App project in Xcode.
  2. Open Main.storyboard and delete the main window.
  3. Open AppDelegate.swift. This is where all your code will go.
  4. Create a variable to hold your NSStatusItem: var statusItem: NSStatusItem?
  5. In applicationDidFinishLaunching(_:):
    • Get the status bar: let statusBar = NSStatusBar.system
    • Create the status item: statusItem = statusBar.statusItem(withLength: NSStatusItem.variableLength)
    • Check if statusItem?.button is not nil.
    • Set its title: statusItem?.button?.title = "Hello"
    • Create the menu and the “Quit” item. Use @objc selectors for actions.
    • Assign the menu to the status item: statusItem?.menu = yourMenu

To quit the application, the menu item’s action should call NSApplication.shared.terminate(self).

Learning milestones:

  1. The app launches without a window → You’ve correctly configured a background application.
  2. Text appears in the menu bar → You have successfully created and configured an NSStatusItem.
  3. A menu appears on click → You can build and attach basic UI.
  4. The “Quit” button works → You understand how to connect UI actions to application logic.

Project 2: Live CPU Usage Monitor

  • File: LEARN_MACOS_STATUS_BAR_APPS.md
  • Main Programming Language: Swift
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Timers / System Monitoring
  • Software or Tool: Xcode
  • Main Book: “The Swift Programming Language” book (for Timer and concurrency).

What you’ll build: A status bar app that displays the current CPU usage as a percentage (e.g., “CPU: 15%”). The text will update every few seconds.

Why it teaches the fundamentals: Static text is easy. This project teaches you how to dynamically update the status item’s content in response to changing data, which is the core of most utility apps.

Core challenges you’ll face:

  • Running a background task → maps to using a Timer to fire an event periodically.
  • Fetching system information → maps to learning how to query macOS for system-level data like CPU load. (This is non-trivial and a great learning exercise).
  • Updating the UI from a timer → maps to ensuring UI updates happen on the main thread.
  • Formatting the output string → maps to basic string manipulation to create a clean display.

Key Concepts:

  • Timer: Apple Developer Documentation.
  • Host Information APIs: You will need to research how to get CPU usage. This often involves using C-level APIs like host_processor_info.
  • Grand Central Dispatch (GCD): “Concurrency in Swift: Things Every Swift Developer Should Know” by anuragajwani.com (for DispatchQueue.main.async).

Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 1.

Real world outcome: A live-updating percentage in your menu bar, giving you a real-time glimpse into your system’s performance.

                  [] [File] ... [Wi-Fi] [CPU: 12%] [Date]
                                     (updates every 2s)

Implementation Hints:

In your AppDelegate, after setting up the status item:

  1. Create a Timer.
    // This is conceptual.
    Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in
        // In this block, you will fetch the CPU usage.
        // Then, update the UI on the main thread.
    }
    
  2. For fetching CPU usage, you’ll find that there’s no simple Swift API. You’ll need to dive into some C-based APIs. A search for “swift get cpu usage macos” will lead you to solutions involving host_statistics. This is a fantastic real-world problem-solving exercise.
  3. The function to get CPU usage will be complex. Once you have it, call it inside the timer’s block.
  4. To update the UI, dispatch back to the main queue:
    // Conceptual
    let usage: Double = self.getCpuUsage()
    let percentage = String(format: "%.0f%%", usage * 100)
    DispatchQueue.main.async {
        self.statusItem?.button?.title = "CPU: \(percentage)"
    }
    

Learning milestones:

  1. The status item updates on a schedule → You have mastered using Timer for background tasks.
  2. You can retrieve a system metric → You’ve learned how to dig for and use lower-level macOS APIs.
  3. The UI updates smoothly without freezing → You understand the importance of the main thread for UI work.
  4. The app provides accurate, real-time information → You’ve built your first genuinely useful utility.

Project 3: Modern Popover App with SwiftUI

  • File: LEARN_MACOS_STATUS_BAR_APPS.md
  • Main Programming Language: Swift
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: SwiftUI / UI Kit Integration
  • Software or Tool: Xcode
  • Main Book: “Hacking with macOS” by Paul Hudson.

What you’ll build: A modern status bar app that, when clicked, shows a custom user interface inside a popover instead of a simple menu. The popover will contain a SwiftUI view with a slider and a text label that reflects the slider’s value.

Why it teaches the fundamentals: This is the standard for modern menu bar apps. It teaches you how to bridge the gap between the older AppKit framework (which manages the status item) and the modern SwiftUI framework (which you’ll use to build your UI).

Core challenges you’ll face:

  • Creating and managing an NSPopover → maps to the object that displays floating content.
  • Hosting a SwiftUI View in AppKit → maps to using NSHostingController to wrap your SwiftUI view.
  • Toggling the popover → maps to showing the popover when the status item is clicked, and hiding it when clicked again or when the user clicks away.
  • Passing data between SwiftUI and AppKit → maps to basic state management and data binding concepts.

Key Concepts:

  • NSPopover: Apple Developer Documentation.
  • NSHostingController: Apple’s official guide on SwiftUI interoperability.
  • SwiftUI State Management (@State): “Hacking with Swift” by Paul Hudson.

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1, basic SwiftUI knowledge.

Real world outcome: Clicking your status bar icon (which you should change to an SF Symbol like “gearshape.fill”) will reveal a sleek, interactive popover.

                  [] [File] ... [Wi-Fi] [⚙️] [Date]
                                           |
                                      ┌───────────┐
                                      │  Value: 50│
                                      │  <──■───> │
                                      └───────────┘

Implementation Hints:

  1. In AppDelegate, create properties for the popover: var popover: NSPopover?
  2. Create a new SwiftUI file for your UI, e.g., ContentView.swift.
    // ContentView.swift
    struct ContentView: View {
        @State private var sliderValue = 50.0
        var body: some View {
            VStack {
                Text("Value: \(Int(sliderValue))")
                Slider(value: $sliderValue, in: 0...100)
            }.padding()
        }
    }
    
  3. In applicationDidFinishLaunching(_:):
    • Create the popover: let popover = NSPopover()
    • Set its behavior: popover.behavior = .transient (so it closes when you click away).
    • Set its content: popover.contentViewController = NSHostingController(rootView: ContentView())
    • Store it: self.popover = popover
  4. Instead of setting a menu on the statusItem, give its button an action.
    // Conceptual
    statusItem?.button?.action = #selector(togglePopover(_:))
    
    @objc func togglePopover(_ sender: Any?) {
        if let button = statusItem?.button {
            if popover.isShown {
                popover.performClose(sender)
            } else {
                popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
            }
        }
    }
    

    This togglePopover logic is crucial and often tricky to get right. You may need to track the popover’s state.

Learning milestones:

  1. A popover appears on click → You can create and present an NSPopover.
  2. The popover contains a SwiftUI view → You’ve successfully bridged AppKit and SwiftUI.
  3. The popover closes when you click away → You understand popover behaviors.
  4. The UI inside the popover is interactive → Your SwiftUI state management is working correctly.

Project 4: Clipboard History Manager

  • File: LEARN_MACOS_STATUS_BAR_APPS.md
  • Main Programming Language: Swift
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: System Events / Data Management
  • Software or Tool: Xcode
  • Main Book: “Advanced Swift” by objc.io.

What you’ll build: A utility that monitors your clipboard. Every time you copy text, the app saves it. Clicking the status bar icon opens a popover listing the last 10 copied items. Clicking an item in the list copies it back to your clipboard.

Why it teaches the fundamentals: This is a classic, highly useful status bar app. It teaches you how to listen to system-wide events, manage a dynamic collection of data, and interact with the system clipboard (NSPasteboard).

Core challenges you’ll face:

  • Monitoring the Pasteboard → maps to using a Timer to periodically check the NSPasteboard for changes.
  • Managing a Data Model → maps to creating an array to store clipboard history and an ObservableObject to publish changes to your SwiftUI view.
  • Displaying a Dynamic List in SwiftUI → maps to using List or ForEach to display the history array.
  • Writing to the Pasteboard → maps to programmatically setting the clipboard content when a user clicks an item.

Key Concepts:

  • NSPasteboard: Apple Developer Documentation.
  • ObservableObject and @Published: The foundation of SwiftUI’s data flow.
  • SwiftUI List: For creating scrollable lists of data.

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 3, solid understanding of SwiftUI data flow.

Real world outcome: A powerful utility that enhances your daily workflow by giving you access to previously copied items.

                     [] [File] ... [Wi-Fi] [📋] [Date]
                                             |
                                        ┌───────────────┐
                                        │ Clipboard     │
                                        │───────────────│
                                        │ • The quick…  │
                                        │ • Hello World │
                                        │ • import SwiftUI │
                                        │ • …           │
                                        └───────────────┘

Implementation Hints:

  1. Create a data model class.
    // ClipboardHistory.swift
    class ClipboardHistory: ObservableObject {
        @Published var items: [String] = []
        // ... methods to add items, etc.
    }
    
  2. In your AppDelegate, create an instance of this class and inject it into your SwiftUI view.
    // Conceptual
    let history = ClipboardHistory()
    popover.contentViewController = NSHostingController(rootView: ContentView().environmentObject(history))
    
  3. Set up a Timer to check the clipboard. A common technique is to store the changeCount of the pasteboard and compare it.
    // Conceptual Timer Block
    if pasteboard.changeCount != lastChangeCount {
        if let copiedString = pasteboard.string(forType: .string) {
            // Add to history model (on main thread)
            history.addItem(copiedString)
        }
        lastChangeCount = pasteboard.changeCount
    }
    
  4. Your ContentView will use @EnvironmentObject to access the history and display it in a List. Each item in the list should be a Button that writes the corresponding text back to the NSPasteboard.

Learning milestones:

  1. The app correctly detects new copied text → Your pasteboard monitoring logic is working.
  2. The popover UI updates to show the new item → Your data flow from the model to the SwiftUI view is correct.
  3. The list can be scrolled → You’ve handled a dynamic list of content.
  4. Clicking an item copies it back to the clipboard → You have completed the full interaction loop.

Project 5: Pomodoro Timer

  • File: LEARN_MACOS_STATUS_BAR_APPS.md
  • Main Programming Language: Swift
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: State Machines / User Notifications
  • Software or Tool: Xcode
  • Main Book: “Design Patterns in Swift” by Adam Freeman.

What you’ll build: A Pomodoro timer that lives in the menu bar. It will show the remaining time for the current session (e.g., “24:15”). The popover UI will have buttons to start, pause, and reset the timer. When a session ends, the app will send a user notification.

Why it teaches the fundamentals: This project is all about state management. You’ll learn how to model the different states of an application (e.g., idle, working, paused, break) and manage transitions between them, a core skill for any non-trivial app.

Core challenges you’ll face:

  • Building a State Machine → maps to using an enum to represent the timer’s state and controlling transitions carefully.
  • Managing Timers Accurately → maps to handling the start/stop logic of Timer and calculating the remaining time precisely.
  • Sending User Notifications → maps to using the UserNotifications framework to alert the user when a work or break session is complete.
  • Updating UI in Real-Time → maps to ensuring both the status bar text and the popover UI are always in sync with the timer’s state.

Key Concepts:

  • State Machines: A fundamental CS concept. “Modeling State in Swift” by John Sundell provides a good overview.
  • UserNotifications framework: Apple’s official documentation.
  • SwiftUI Data Flow: You will need to make your state machine an ObservableObject to drive the UI.

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 3.

Real world outcome: A focused productivity tool that helps you manage your work sessions directly from your menu bar.

                  [] [File] ... [Wi-Fi] [🍅 23:42] [Date]
                                             |
                                        ┌───────────────┐
                                        │   Work Session  │
                                        │     23:42       │
                                        │ [Pause] [Reset] │
                                        └───────────────┘

When the timer finishes, a notification appears: “Work session complete! Time for a break.”

Implementation Hints:

  1. Define your state enum:
    enum TimerState {
        case idle, running, paused, breakTime
    }
    
  2. Create a TimerModel as an ObservableObject that holds the @Published state, remaining time, etc.
  3. Your ContentView will have buttons that call methods on the TimerModel (e.g., model.start(), model.pause()).
  4. A Timer will be the engine. When it fires (e.g., every second), it will decrement the remaining time in the model.
  5. When the remaining time hits zero, the model should transition its state (e.g., from .running to .breakTime) and call a function to schedule a UNUserNotification.
  6. You’ll need to request notification permissions from the user when the app first launches.

Learning milestones:

  1. The timer counts down correctly in the menu bar → Your core timer logic and UI updates are working.
  2. Start, pause, and reset buttons work as expected → Your state machine correctly handles all transitions.
  3. A user notification is delivered when the timer completes → You have successfully integrated with a system service.
  4. The app’s state is consistent across the status bar and the popover → You have mastered syncing your model with multiple UI components.

Summary

| Project | Main Language | | :— | :— | | 1. “Hello, Menu Bar” | Swift | | 2. Live CPU Usage Monitor | Swift | | 3. Modern Popover App with SwiftUI | Swift | | 4. Clipboard History Manager | Swift | | 5. Pomodoro Timer | Swift |