Although I like (and up-voted) the accepted answer, I found a way for a view to respond to right-mouse button events without having to conform to NSViewRepresentable
. This approach integrated more cleanly into my app, so it may be worth considering as one possibility for someone else facing this problem.
My solution involves first being willing to accept the convention that right-clicking and control-left-clicking are traditionally treated as equivalent in macOS. This solution doesn't allow for handling control-right-clicking differently from control-left-clicking. But any other modifiers are handled, since it only adds .control
to them and converts it to a left-click.
This might break SwiftUI contextual menus, if you use them. I haven't tested that.
So idea is to translate right mouse button events into left mouse button events with a control-key modifier.
To accomplish this I subclassed NSHostingView
, and provided a convenience extension on NSEvent
// -------------------------------------
fileprivate extension NSEvent
{
// -------------------------------------
var translateRightMouseButtonEvent: NSEvent
{
guard let cgEvent = self.cgEvent else { return self }
switch type
{
case .rightMouseDown: cgEvent.type = .leftMouseDown
case .rightMouseUp: cgEvent.type = .leftMouseUp
case .rightMouseDragged: cgEvent.type = .leftMouseDragged
default: return self
}
cgEvent.flags.formUnion(.maskControl)
guard let nsEvent = NSEvent(cgEvent: cgEvent) else { return self }
return nsEvent
}
}
// -------------------------------------
class MyHostingView<Content: View>: NSHostingView<Content>
{
// -------------------------------------
@objc public override func rightMouseDown(with event: NSEvent) {
super.mouseDown(with: event.translateRightMouseButtonEvent)
}
// -------------------------------------
@objc public override func rightMouseUp(with event: NSEvent) {
super.mouseUp(with: event.translateRightMouseButtonEvent)
}
// -------------------------------------
@objc public override func rightMouseDragged(with event: NSEvent) {
super.mouseDragged(with: event.translateRightMouseButtonEvent)
}
}
Then in AppDelegate.didFinishLaunching
I changed
window.contentView = NSHostingView(rootView: contentView)
to
window.contentView = MyHostingView(rootView: contentView)
Of course one would have to make similar changes in any other code that might refer to NSHostingView
. Often the reference in AppDelegate
is the only one, but in a significant project there might be others.
The right mouse button events then appear in SwiftUI code as a TapGesture
with a .control
modifier.
Text("Right-clickable Text")
.gesture(
TapGesture().modifiers(.control)
.onEnded
{ _ in
print("Control-Clicked")
}
)
.onTapGesture()
check it out. – Hamamelidaceous.gesture(TapGesture().onEnded(.......))
– Termless