AppDelegate.swift

2.2 KB · raw

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}