Implementing right-click for NSButton
source link: https://www.jessesquires.com/blog/2019/08/15/implementing-right-click-for-nsbutton/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
15 Aug 2019
software-dev appkit, apps, macos, uikit
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK