1

The NSStatusItem menu shows correctly but when I click on menu the action not fire.

Since the app is compatible with Mac Catalyst, I created framework then passing Framework to main app in order to show the menu of NSStatusItem, it work correctly but I have the issue for action of the menu that doesn't work.

Here is my code:

@objc class AppKitController: NSObject {
    var statusBarItem: StatusBarItemControler!

    override init() {
        super.init()
        
        print("[AppKitController] Loaded successfully")
        self.statusBarItem = StatusBarItemControler()
        self.statusBarItem.updateTitle()
        self.statusBarItem.updateMenu()
    }
}

class StatusBarItemControler {
    let item: NSStatusItem

    init() {
        self.item = NSStatusBar.system.statusItem(
            withLength: NSStatusItem.variableLength
        )
        let statusBarMenu = NSMenu(title: "APPMenu")
        self.item.menu = statusBarMenu
        
    }

    func updateTitle() {
        let title = "AppMenu"
        print("Update title")

        DispatchQueue.main.async {
            if let button = self.item.button {
                button.title = "\(title)"
                button.target = self
                
            }
        }
    }

    func updateMenu() {
        if let statusBarMenu = self.item.menu {
            statusBarMenu.autoenablesItems = false
            statusBarMenu.removeAllItems()
            
            statusBarMenu.addItem(NSMenuItem.separator())
            statusBarMenu.addItem(NSMenuItem.separator())
            self.createPreferencesSection()
        }
    }

    func createPreferencesSection() {
        self.item.menu!.addItem(
            withTitle: "Open",
            action: #selector(openPrefecencesWindow),
            keyEquivalent: ",")
        self.item.menu!.addItem(
            withTitle: "Quit",
            action: #selector(quit),
            keyEquivalent: "q")
    }
    
    
    @objc func openPrefecencesWindow(_: NSStatusBarButton?) {
        print("Open preferences window")
    
        }
    
    @objc func quit(_: NSStatusBarButton?) {
        print("Open preferences window")
        }

}
Alexander
  • 59,041
  • 12
  • 98
  • 151
Steven
  • 762
  • 1
  • 10
  • 27
  • You only set the target, but never set the action on your button – Alexander Dec 28 '22 at 19:39
  • I set the action “openPrefecencesWindow” – Steven Dec 28 '22 at 21:54
  • I was referreing to `self.item.button`. I didn't realize your issue was with the "Open" button. Check the console, are you getting an "Unrecognized selector sent to instance" error? – Alexander Dec 28 '22 at 22:50
  • Do you set the target of the menu items? – Willeke Dec 29 '22 at 05:54
  • @Willeke, I set the target in updateTitle() function. – Steven Dec 29 '22 at 14:56
  • @Alexander: here is the error: Assertion failure in -[UINSResponderProxy _initWithWrappedResponder:orMenuProxy:forAction:sender:], UINSResponderProxy.m:332 2022-12-29 15:57:44.988162+0100 App[889:11232] [General] An uncaught exception was raised 2022-12-29 15:57:44.988263+0100 App[889:11232] [General] -[UINSResponderProxy _initWithWrappedResponder:orMenuProxy:forAction:sender:]: UINSResponderProxy is improperly wrapping a responder that does not respond to the action. – Steven Dec 29 '22 at 14:58
  • Ah yes, so menu items send their "action" to the responder chain. When you give it `action: #selector(openPrefecencesWindow)`, you're telling it: "When clicked, send the message "openPrefecencesWindow" to the responder chain". If you want your app delegate object to be the one to respond to this message (i.e. have its method invoked), then that would only work if your made your app delegate a link in the responder chain. See https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events – Alexander Dec 29 '22 at 15:01
  • @Alexander, what you mean exactly " only work if your made your app delegate a link in the responder chain"? – Steven Dec 29 '22 at 15:26
  • Did you read the docs I sent? "UIApplication object. The next responder is the app delegate, but only if the app delegate is an instance of UIResponder and isn’t a view, view controller, or the app object itself." – Alexander Dec 29 '22 at 15:33
  • Thank you @Alexander but I didn't understand it very well, I will try some way to make it work. If you mean to add it in app delegate, thats not possible since my app support MacCatylist that is why I did a work around to add framework and pass the framework to main application. – Steven Dec 29 '22 at 15:43
  • @Steven You'll need to understand the responder chain to be able to use toolbars and menu bars on macOS. It's a pretty core part of the SDK that you should learn. It's essentially a linked list of "responders". A message sent to the responder chain is sent to the last item on the chain. If it responds to that message, it'll handle it, otherwise it'll forward it up the chain, hoping to ultimately find a responder which responds to the message. The error you're getting comes from the fact that no responders on your responder chain respond to `#selector(openPrefecencesWindow)`. – Alexander Dec 29 '22 at 15:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/250737/discussion-between-alexander-and-steven). – Alexander Dec 29 '22 at 15:48
  • I'm asking about the the target of the menu items, not the target of the button. – Willeke Dec 31 '22 at 09:08

1 Answers1

0

Thank you @Alexander, I have found the solution and it works.

 class AppKitController: NSObject,NSApplicationDelegate,NSWindowDelegate {
 
     
     let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
     
     override init() {
         super.init()
         
         NSApplication.shared.delegate = self
         NSApplication.shared.mainWindow?.delegate = self
         statusItem.button?.title = "Your_App_Name"
         
         statusItem.menu = createMenu()
         print("[AppKitController] Loaded successfully")
       
     }
     
     func createMenu() -> NSMenu{
         let menu = NSMenu()
         let openMenuItem = menu.addItem(withTitle: "Open", action: #selector(openMenu), keyEquivalent: "")
         openMenuItem.target = self
         return menu
         
     }
 
     @objc func openMenu(_ sender:Any?){
         print("Open menu called")
         
     }
     func windowShouldClose(_ sender: NSWindow) -> Bool {
         print("Window should close")
         return false
     }
}
Steven
  • 762
  • 1
  • 10
  • 27