This isn’t complicated, but I found it confusing. Perhaps I am spoiled by the more modern APIs in UIKit. When writing Lucifer, a menu bar app, I wanted to have different actions for left-clicking and right-clicking on the button in the menu bar. To my surprise, this was much more cumbersome than I expected.

An aside: for a menu bar item, you create an NSStatusItem (a subclass of NSObject), which has an NSStatusBarButton property, which is an NSButton. A bit odd, similar to UINavigationItem on iOS, which also inherits from NSObject.

In UIKit, responding to different events for the same button (or any UIControl) is straight-forward. You can add as many target-action pairs as you like, each responding to a specific UIControl event.

let button = UIButton(frame: frame)

button.addTarget(self, action: #selector(touchUp), for: .touchUpInside)

button.addTarget(self, action: #selector(touchDown), for: .touchDown)

Furthermore, in UIKit it is even possible to (optionally) pass the UIControl.Event to the specified selector. Conventionally, most people usually only pass the sender.

@objc
func onTouchEvent(sender: Any, action: UIControl.Event) {
    // do something
}

The APIs in AppKit are much less intuitive. A UIControl is only associated with a single target and action, and you cannot pass the event to the specified selector, only the sender.

let button = NSButton(title: title, target: self, action: #selector(onClick))

To achieve similar functionality as UIKit — specifically, responding differently to a left-click and right-click — you must specify on which events to send the action message.

button.sendAction(on: [.leftMouseDown, .rightMouseDown])

Then, in the function that you have specified to handle events, you must query NSApp.currentEvent.

extension NSEvent {
    var isRightClick: Bool {
        let rightClick = (self.type == .rightMouseDown)
        let controlClick = self.modifierFlags.contains(.control)
        return rightClick || controlClick
    }
}

@objc
func onClick() {
    if let event = NSApp.currentEvent, event.isRightClick {
        // handle right-click
    } else {
        // handle left-click
    }
}

This feels odd and clunky — maybe I am merely confronting the differences between developing for macOS instead of iOS — but it appears to be “the right way” to do this. I could not find much written about this online. If there is a better way to do this, please let me know. It would also be interesting to know why these APIs are designed this way.