import AppKit class AppDelegate: NSObject, NSApplicationDelegate { // Optional backing storage rather than implicitly-unwrapped optionals: // before `applicationDidFinishLaunching` runs (or after `invalidate`), // there is genuinely no status item or menu controller to hand out, and // reading them through a normal optional forces the lifecycle through // a single funnel instead of letting any caller trap on a half-built // delegate. private var statusItem: NSStatusItem? private var menuController: MenuController? private let removeStatusItem: (NSStatusItem) -> Void init(removeStatusItem: @escaping (NSStatusItem) -> Void = { NSStatusBar.system.removeStatusItem($0) }) { self.removeStatusItem = removeStatusItem super.init() } func applicationDidFinishLaunching(_ notification: Notification) { // Create the status item in the menu bar let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) if let button = item.button { // Use a system symbol for the icon button.image = NSImage( systemSymbolName: "photo.on.rectangle.angled", accessibilityDescription: "Haven") } // Initialize the menu controller menuController = MenuController(statusItem: item) statusItem = item } /// Read-only access to the status item created during launch. Returns /// `nil` when the delegate has not finished launching yet (or after /// `invalidate()` has been called). Callers that need the item to be /// present must consult this guard and fail gracefully otherwise. var currentStatusItem: NSStatusItem? { statusItem } /// Read-only access to the menu controller created during launch. /// Returns `nil` before launch finishes so callers do not accidentally /// observe a half-built controller. var currentMenuController: MenuController? { menuController } /// Tear down status-item and menu-controller state. Useful for tests /// that want to recreate the delegate cleanly and for any future code /// path that needs to dispose of the menu bar item explicitly. func invalidate() { if let item = statusItem { removeStatusItem(item) } statusItem = nil menuController = nil } }