swiftnsstatusitemnsstatusbar

Redirect button action to another function in Swift


This might be an easy question, but I am new to Swift and do not know for which terms to google for.

For a menu bar application, I have implemented a custom function called doSomething that works just fine when it is bound to some button:

Class MainViewController:
{
    @IBAction func doSomething(sender: NSButton) 
    {
        // Do something when NSButton is pressed
    }
}

However, I need to distinguish between left- and right click on the button, which in my case is a NSStatusBarButton. Following the suggestion from this answer, I have written the following into my AppDelegate.swift:

@NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {

    let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-2)
    var mainViewController: MainViewController?


    func applicationDidFinishLaunching(notification: NSNotification) 
    {
        if let button = statusItem.button 
        {
            button.action = #selector(customClickAction)
            button.sendActionOn(Int(NSEventMask.RightMouseDownMask.rawValue | NSEventMask.LeftMouseDownMask.rawValue))
        }
    }


    func customClickAction(sender: NSButton) 
    {
        let event:NSEvent! = NSApp.currentEvent!

        if (event.type == NSEventType.RightMouseDown) 
        {
            print("Right mouse button down")
        } 
        else if (event.type == NSEventType.LeftMouseDown)
        {
            print("Left mouse button down")
            mainViewController?.doSomething(_:) // THIS DOES NOT WORK
         }
    }
}

The above code snippet gives me the error message 'Expression resolves to an unused function' in XCode. I cannot figure out how to properly call the function doSomething from the MainViewController class within the customClickAction function, or equivalently, how to redirect the action of the statusItem.button via customClickAction to doSomething. I apologize if this question might seem too trivial for the Swift experts, but I am really in despair trying to figure this one out.

EDIT:

If the function customClickAction was not existing, I would simply write button.action = #selector(mainViewController?.show(_:)) in applicationDidFinishLaunching to call the function and everything works. However, part of my problem is that doing the same in my custom function would overwrite the binding once the left mouse button has been pressed for the first time.


Solution

  • Here is a question from someone who had the same Expression resolves to an unused function problem. In his/her case the problem was that the functions was called without the () after the function, so stop instead of stop() (if that makes sense).

    OK, so now that we know what the compiler is trying to tell us, we can try to figure out what to do to solve it.

    In your case the problem is that you need to send a sender parameter to your doSomething method, and that parameter must be of type NSButton as you've declared your self.

    @IBAction func doSomething(sender: NSButton) 
    {
        // Do something when NSButton is pressed
    }
    

    So, in your case, if you just pass the sender which you get as a parameter to your customClickAction along like so:

    func customClickAction(sender: NSButton)
    {
        let event:NSEvent! = NSApp.currentEvent!
    
        if (event.type == NSEventType.RightMouseDown)
        {
            print("Right mouse button down")
        }
        else if (event.type == NSEventType.LeftMouseDown)
        {
            print("Left mouse button down")
            mainViewController?.doSomething(sender)
        }
    }
    

    Then the compiler seems happy.

    Here is the entire AppDelegate

    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
        let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-2)
        var mainViewController: MainViewController? = MainViewController() //initialized
    
    
        func applicationDidFinishLaunching(notification: NSNotification)
        {
            if let button = statusItem.button
            {
                button.action = #selector(customClickAction)
                button.sendActionOn(Int(NSEventMask.RightMouseDownMask.rawValue | NSEventMask.LeftMouseDownMask.rawValue))
            }
        }
    
    
        func customClickAction(sender: NSButton)
        {
            let event:NSEvent! = NSApp.currentEvent!
    
            if (event.type == NSEventType.RightMouseDown)
            {
                print("Right mouse button down")
            }
            else if (event.type == NSEventType.LeftMouseDown)
            {
                print("Left mouse button down")
                mainViewController?.doSomething(sender)
            }
        }
    }
    

    Hope that helps you.

    Followup to your edit

    You write

    If the function customClickAction was not existing, I would simply write button.action = #selector(mainViewController?.show(_:)) in applicationDidFinishLaunching to call the function and everything works.

    Its possible that I'm misunderstanding everything here, but why don't you then "just" assign the doSomething method to your button.action and then moves the check for left or right button to that method?

    So you'd say:

    button.action = #selector(mainViewController?.doSomething(_:))
    

    and your doSomething would look like this:

        @IBAction func doSomething(sender: NSButton)
    {
        // Do something when NSButton is pressed
        let event:NSEvent! = NSApp.currentEvent!
    
        if (event.type == NSEventType.RightMouseDown)
        {
            print("Right mouse button down")
        }
        else if (event.type == NSEventType.LeftMouseDown)
        {
            print("Left mouse button down")
        }
    }
    

    "Left mouse button down" and "Right mouse button down" shows up in my console if I do that.

    Swift 3 update

    As @ixany writes in the comments:

    Is it me or Swift 3? :) Seems that your solution needs to be updated to Swift 3 since I'm not able to adapt it using the current Xcode Beta

    I've tried to upgrade the syntax to Swift 3.0 but there seems to be some problems with it.

    The first problem is that I could not go directly to the doSomething method of MainViewController when assigning the selector, so I added a "local" step so to speak.

    The AppDelegate now looks like this:

    import Cocoa
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
        @IBOutlet weak var window: NSWindow!
    
        let statusItem = NSStatusBar.system().statusItem(withLength: -2)
        var mainViewController: MainViewController? = MainViewController()
    
        func applicationDidFinishLaunching(_ aNotification: Notification) {
            if let button = statusItem.button
            {
                button.action = #selector(AppDelegate.localButtonAction(sender:))
                button.sendAction(on: [NSEventMask.leftMouseDown, NSEventMask.rightMouseDown])
            }
        }
    
        func localButtonAction(sender: NSStatusBarButton) {
            mainViewController?.doSomething(sender: sender)
        }
    }
    

    (Notice the ´localButtonAction´ method, I hope someone else has a prettier way to do this)

    And the MainViewController looks like this:

    import Cocoa
    
    class MainViewController: NSViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do view setup here.
        }
    
        @IBAction func doSomething(sender: NSStatusBarButton) {
            // Do something when NSButton is pressed
            let event:NSEvent! = NSApp.currentEvent!
            if (event.type == NSEventType.rightMouseDown)
            {
                print("Right mouse button down")
            }
            else if (event.type == NSEventType.leftMouseDown)
            {
                print("Left mouse button down")
            }
        }    
    }
    

    The problem with this is that the event is never of type rightMouseDown as far as I can see. I'm probably doing something wrong and I hope that someone else can make this work, but now it compiles at least and is Swift 3.0 syntax (using Xcode 8.0 - beta 6 :))

    Hope that helps you @ixany