Article
Getting started
Add the package and show a first pill at the notch.
Add the package
// Package.swift
dependencies: [
.package(url: "https://github.com/polytopic-systems/polytopic-notch.git", from: "1.1.0"),
],
targets: [
.target(name: "MyApp", dependencies: [.product(name: "PolytopicNotch", package: "polytopic-notch")]),
]In Xcode: File › Add Package Dependencies…, paste the URL, Up to Next Major Version from 1.1.0.
macOS 26 or later, Swift 6, no dependencies, no Accessibility or Screen Recording permission.
Your first pill
import AppKit
import PolytopicNotch
import SwiftUI
@MainActor
final class MyNotch {
let content = NotchContent()
let settings = InMemoryNotchSettings() // or your own NotchSettingsStore (Recipes › Settings)
let neighbors = NotchNeighborMonitor()
lazy var controller = NotchPanelController(content: content, settings: settings, neighbors: neighbors)
lazy var prompts = NotchPromptWindowController(content: content) { [unowned self] in controller.pillShowsPrompt }
func start() {
content.symbol = "waveform"
content.title = "My App"
content.status = .quiet // a meter and a status dot beside the notch
content.expanded = NotchBody(height: 160) { Text("Shown on hover").padding() }
controller.start()
observe { [prompts] in prompts.update() } // a question the pill can't show floats in a window
}
}
/// Runs `apply` again whenever an observable value it read changes.
@MainActor
func observe(_ apply: @escaping @MainActor () -> Void) {
withObservationTracking(apply) { Task { @MainActor in observe(apply) } }
}Start it once the app has launched:
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor private var delegate: AppDelegate
var body: some Scene {
MenuBarExtra("My App", systemImage: "waveform") {
Button("Post a Notice") { delegate.notch.post("Something happened") }
Button("Quit") { NSApp.terminate(nil) }
}
}
}
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
let notch = MyNotch()
func applicationDidFinishLaunching(_ notification: Notification) {
notch.start() // after launch, not from a SwiftUI initializer
}
}post(_:) is the notice recipe in Recipes.
NotchContentis all the pill reads from your app. Change it and the pill follows.- Keep
MyNotchfor as long as the app runs. - Started from a
@Stateinitial value, the controller waits until launch has finished: laying out the pill inside SwiftUI's scene update would abort the app. - On a Mac without a notch, or an external display, the same pill hangs below the menu bar.
What NotchContent holds
| Field | What it does |
|---|---|
status |
off, quiet (meter and dot), showing (the showing body drops down), attention (showing, in the attention colour) |
showing, expanded, prompt |
the bodies: a notice, the hover panel, a question (NotchBody) |
symbol, title |
your SF Symbol; the title in the band where there's no notch |
level |
0...1, for the meter |
busy |
loading bars: something the user waits for |
dot, lineColor |
your own colours for the status dot and the quiet line |
want |
how much you want the notch when apps share it: none < quiet < notice < live (The dock) |
Next
- Recipes: notices, panels, live lists, menus, questions, tabs, the dock, themes, logging, Settings, tests.
- Your own panel: what a body can read and do.
Examples/NotchDemo: a menu bar app in one file that does all of it. In that folder:swift run --scratch-path ~/Library/Caches/notch-demo-build NotchDemo.