Search code examples
swiftmacoscocoanspopoverlsuielement

How can I make my app behave like I'm changing Application is agent(UIElement) at runtime using Swift?


I am coding a Mac app that is an NSPopover on the right side of the menu bar (Application is agent(UIElement) is set to YES). I allow the user to detach the popover by clicking and dragging it downward, which puts the app inside of a window. This is working fine; however, when the app is dragged off of the menu bar and made into a window, I would like my app's icon to come up in the dock, as well as show app-specific menus on the left side of the menu bar, as if Application is agent(UIElement) is set to NO. Conversely, when the window is closed and the app is returned to a popover in the menu bar, I would like my app's icon to disappear from the dock and no longer show app-specific menus on the left side of the menu bar (Application is agent(UIElement) is set back to YES).

From this question, I understand that changing Application is agent(UIElement) at runtime isn't possible. However, the answer given is in Objective-C, and the last function appears to be depreciated since OS X 10.9. How can I make my app have the same behavior as changing Application is agent(UIElement) at runtime using Swift?

I know that showing the app icon/menu bar menus would happen in windowDidBecomeMain and hiding the app icon/menu bar menus would happen in windowWillClose.

Thanks.


Solution

  • It took a lot of trial and error, but I finally figured it out. Instead of using Application is agent(UIElement), you use NSApp.setActivationPolicy. This is now my code. In the App Delegate:

    var isWindow = false
    
    class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
    
        let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
        let popover = NSPopover()
    
        func applicationDidFinishLaunching(_ aNotification: Notification) {
            // Insert code here to initialize your application
    
            NSApp.setActivationPolicy(.accessory)
    
            if let button = statusItem.button {
                button.image = NSImage(named: "StatusBarImage")
                button.action = #selector(togglePopover(_:))
            }
            popover.contentViewController = MainViewController.loadController()
            popover.delegate = self
            popover.animates = false
            popover.behavior = .transient
        }
    
        @objc func togglePopover(_ sender: Any?) {
            if popover.isShown == true {
                popover.performClose(sender)
            } else if detachedWindowController.window!.isVisible {
                detachedWindowController.window?.setIsVisible(false)
                isWindow = true
            } else if isWindow == true {
                detachedWindowController.window?.setIsVisible(true)
            } else {
                if let button = statusItem.button {
                    popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
                }
            }
        }
    
        lazy var detachedWindowController: DetachedWindowController = {
            let detachedWindowController = DetachedWindowController(windowNibName: "DetachedWindowController")
            detachedWindowController.contentViewController = MainViewController.loadController()
            return detachedWindowController
        }()
    
        func popoverShouldDetach(_ popover: NSPopover) -> Bool {
            return true
        }
    
        func detachableWindow(for popover: NSPopover) -> NSWindow? {
            return detachedWindowController.window
        }
    }
    

    In the DetachedWindowController:

    class DetachedWindowController: NSWindowController, NSWindowDelegate {
    
        @IBOutlet var detachedWindow: NSWindow!
    
        override func windowDidLoad() {
            super.windowDidLoad()
    
            detachedWindow.delegate = self
        }
    
        func windowWillClose(_ notification: Notification) {
            isWindow = false
            NSApp.setActivationPolicy(.accessory)
        }
    
        func windowDidBecomeMain(_ notification: Notification) {
            if NSApp.activationPolicy() == .accessory {
                NSApp.setActivationPolicy(.regular)
            }
        }
    }