AppDelegate.swift
1import AppKit23class AppDelegate: NSObject, NSApplicationDelegate {4 // Optional backing storage rather than implicitly-unwrapped optionals:5 // before `applicationDidFinishLaunching` runs (or after `invalidate`),6 // there is genuinely no status item or menu controller to hand out, and7 // reading them through a normal optional forces the lifecycle through8 // a single funnel instead of letting any caller trap on a half-built9 // delegate.10 private var statusItem: NSStatusItem?11 private var menuController: MenuController?12 private let removeStatusItem: (NSStatusItem) -> Void1314 init(removeStatusItem: @escaping (NSStatusItem) -> Void = {15 NSStatusBar.system.removeStatusItem($0)16 }) {17 self.removeStatusItem = removeStatusItem18 super.init()19 }2021 func applicationDidFinishLaunching(_ notification: Notification) {22 // Create the status item in the menu bar23 let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)2425 if let button = item.button {26 // Use a system symbol for the icon27 button.image = NSImage(28 systemSymbolName: "photo.on.rectangle.angled", accessibilityDescription: "Haven")29 }3031 // Initialize the menu controller32 menuController = MenuController(statusItem: item)33 statusItem = item34 }3536 /// Read-only access to the status item created during launch. Returns37 /// `nil` when the delegate has not finished launching yet (or after38 /// `invalidate()` has been called). Callers that need the item to be39 /// present must consult this guard and fail gracefully otherwise.40 var currentStatusItem: NSStatusItem? { statusItem }4142 /// Read-only access to the menu controller created during launch.43 /// Returns `nil` before launch finishes so callers do not accidentally44 /// observe a half-built controller.45 var currentMenuController: MenuController? { menuController }4647 /// Tear down status-item and menu-controller state. Useful for tests48 /// that want to recreate the delegate cleanly and for any future code49 /// path that needs to dispose of the menu bar item explicitly.50 func invalidate() {51 if let item = statusItem {52 removeStatusItem(item)53 }54 statusItem = nil55 menuController = nil56 }57}