2

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.

Community
  • 1
  • 1
klopps
  • 85
  • 5

1 Answers1

2

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

Community
  • 1
  • 1
pbodsk
  • 6,787
  • 3
  • 21
  • 51
  • Following your suggestion, the error message disappears but the function `doSomething` is not executed at all. Perhaps `sender` is not the correct parameter? – klopps Jun 30 '16 at 08:33
  • I think the reason is, that your `mainViewController` is nil. In the code shown in your question you are not giving the `mainViewController` variable any initial value, so it is always nil. Therefore, when you say `mainViewController?.doSomething(sender)` your `mainViewController` is nil so `doSomething` is not executed. If you aren't already, then you should initialize `mainViewController` before you start using it. – pbodsk Jun 30 '16 at 08:42
  • If it was not for the `customClickAction`, I would simply execute `button.action = #selector(mainViewController?.show(_:))` to call the function and it works even if the `mainViewController` was not initialized. Why do I have to initialize it when I call it from a different function? – klopps Jun 30 '16 at 08:52
  • I just tried changing to just using your selector straight on the `button.action` and you are right, that works. I can not tell you why (because I don't know :)), but in the case where you need the `customClickAction` the problem is that you are in a function (`customClickAction`) and there you are trying to call another function (`doSomething`) on an optional variable (`mainViewController`). If that optional is nil, then the chain stops there. If I initialize `mainViewController` it works and I end up in my `doSomething` method. I've updated my answer so you can see the entire `AppDelegate`. – pbodsk Jun 30 '16 at 09:09
  • Thank you very much for providing a complete answer, it has helped me a lot. – klopps Jun 30 '16 at 12:58
  • 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. – ixany Aug 27 '16 at 09:53
  • @ixany check my updated answer, hope that leads you in the right direction at least – pbodsk Aug 27 '16 at 11:19
  • thank you @pbodsk - my approach was pretty similar! You wrote that `event` is never of type `rightMouseDown`. However, in my case the local function isn’t called at all. I’m not able to call any action from NSStatusBarButton... maybe a bug? – ixany Aug 28 '16 at 08:06
  • @ixany so in your case, the ´localButtonAction´ isn't called? Or is that called but the ´doSomething´ isn't? In my example above I made it all the way to `doSomething`, but the `event` was never of type `rightMouseDown`, although I did press the right mouse button. – pbodsk Aug 28 '16 at 08:23
  • @pbodsk ´localButtonAction´ is not called in my case :'( The only difference is that I declared ´NSStatusBar´ outside of the AppDelegate. – ixany Aug 31 '16 at 07:29
  • Weird. Can you see that you end up inside the `if let button = statusItem.button` in `applicationDidFinishLaunching`, I mean, if you declare your `NSStatusBar` outside of `AppDelegate`, then maybe the reference to it is lost somehow (I'm guessing now...grasping at straws if you couldn't tell :)) – pbodsk Aug 31 '16 at 08:15