如何检测 SwiftUI 中的右键单击?
How can I detect a right-click in SwiftUI?
我写 a simple Mines app 是为了帮助我了解 SwiftUI。因此,我希望主要点击(通常是 LMB)到 "dig"(显示那里是否有地雷),然后二次点击(通常是 RMB)来放置一个标志。
我正在挖掘!但是我不知道如何放置标志,因为我不知道如何检测二次点击。
BoardSquareView(
style: self.style(for: square),
model: square
)
.gesture(TapGesture().modifiers(.control).onEnded(self.handleUserDidAltTap(square)))
.gesture(TapGesture().onEnded(self.handleUserDidTap(square)))
正如我之前暗示的那样,handleUserDidTap
返回的函数在单击时被正确调用,但是 handleUserDidAltTap
返回的函数仅在我按住 Control 键时被调用。这是有道理的,因为代码就是这么写的……但我没有看到任何 API 可以让它注册二次点击,所以我不知道还能做什么。
我也试过这个,但行为似乎是一样的:
BoardSquareView(
style: self.style(for: square),
model: square
)
.gesture(TapGesture().modifiers(.control).onEnded(self.handleUserDidAltTap(square)))
.onTapGesture(self.handleUserDidTap(square))
就目前 SwiftUI 的情况而言,这不可能直接实现。我相信它会在未来,但目前,TapGesture
显然主要集中在没有“右键单击”概念的 iOS 用例上,所以我认为这就是为什么它被忽略了。请注意,“长按”概念是 LongPressGesture
形式的第一个 class 公民,并且几乎专门用于支持该理论的 iOS 上下文。
就是说,我确实想出了一种方法来完成这项工作。您需要做的是退回到旧技术,并将其嵌入到您的 SwiftUI 视图中。
struct RightClickableSwiftUIView: NSViewRepresentable {
func updateNSView(_ nsView: RightClickableView, context: NSViewRepresentableContext<RightClickableSwiftUIView>) {
print("Update")
}
func makeNSView(context: Context) -> RightClickableView {
RightClickableView()
}
}
class RightClickableView: NSView {
override func mouseDown(with theEvent: NSEvent) {
print("left mouse")
}
override func rightMouseDown(with theEvent: NSEvent) {
print("right mouse")
}
}
我对此进行了测试,它在一个相当复杂的 SwiftUI 应用程序中对我有用。这里的基本做法是:
- 将您的听力组件创建为
NSView
。
- 用实现
NSViewRepresentable
. 的 SwiftUI 视图包装它
- 将你的实现放入你想要的 UI 中,就像你对任何其他 SwiftUI 视图所做的那样。
这不是一个理想的解决方案,但它现在可能已经足够好了。我希望这能解决您的问题,直到 Apple 进一步扩展 SwiftUI 的功能。
不幸接受的解决方案不适合我,因为点击 NSStatusItem.button
时没有视觉反馈。对我有用的是检测按钮触摸处理程序中的右键单击:
func setupStatusBarItem() {
statusBarItem.button?.action = #selector(didPressBarItem(_:))
statusBarItem.button?.target = self
statusBarItem.button?.sendAction(on: [.leftMouseDown, .rightMouseDown])
}
@objc func didPressBarItem(_ sender: AnyObject?) {
if let event = NSApp.currentEvent, event.isRightClick {
print("right")
} else {
print("left")
}
}
extension NSEvent {
var isRightClick: Bool {
let rightClick = (self.type == .rightMouseDown)
let controlClick = self.modifierFlags.contains(.control)
return rightClick || controlClick
}
}
灵感来自 article。
尽管我喜欢(并投赞成票)已接受的答案,但我找到了一种视图无需遵守 NSViewRepresentable
即可响应鼠标右键事件的方法。这种方法更干净地集成到我的应用程序中,因此可能值得考虑将其作为面临此问题的其他人的一种可能性。
我的解决方案首先是愿意接受在 macOS 中右键单击和控制左键单击传统上被视为等同的约定。此解决方案不允许以不同于控制左键单击的方式处理控制右键单击。但是任何其他修饰符都会被处理,因为它只会向它们添加 .control
并将其转换为左键单击。
如果您使用 SwiftUI 上下文菜单,可能会 破坏它们。我还没有测试过。
所以想法是使用控制键修饰符将鼠标右键事件转换为鼠标左键事件。
为了实现这一点,我对 NSHostingView
进行了子类化,并在 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)
}
}
然后在AppDelegate.didFinishLaunching
我改了
window.contentView = NSHostingView(rootView: contentView)
至
window.contentView = MyHostingView(rootView: contentView)
当然,必须对可能引用 NSHostingView
的任何其他代码进行类似的更改。通常 AppDelegate
中的引用是唯一的,但在一个重要的项目中可能还有其他引用。
鼠标右键事件随后在 SwiftUI 代码中显示为带有 .control
修饰符的 TapGesture
。
Text("Right-clickable Text")
.gesture(
TapGesture().modifiers(.control)
.onEnded
{ _ in
print("Control-Clicked")
}
)
这并不能完全解决扫雷器用例,但contextMenu
是一种在 SwiftUI macOS 应用程序中处理右键单击的方法。
例如:
.contextMenu {
Button(action: {}, label: { Label("Menu title", systemImage: "icon") })
}
将响应右键单击 macOS 和长按 iOS
将此添加到您的视图中。适用于 macOS
.contextMenu {
Button("Remove") {
print("remove this view")
}
}
我写 a simple Mines app 是为了帮助我了解 SwiftUI。因此,我希望主要点击(通常是 LMB)到 "dig"(显示那里是否有地雷),然后二次点击(通常是 RMB)来放置一个标志。
我正在挖掘!但是我不知道如何放置标志,因为我不知道如何检测二次点击。
BoardSquareView(
style: self.style(for: square),
model: square
)
.gesture(TapGesture().modifiers(.control).onEnded(self.handleUserDidAltTap(square)))
.gesture(TapGesture().onEnded(self.handleUserDidTap(square)))
正如我之前暗示的那样,handleUserDidTap
返回的函数在单击时被正确调用,但是 handleUserDidAltTap
返回的函数仅在我按住 Control 键时被调用。这是有道理的,因为代码就是这么写的……但我没有看到任何 API 可以让它注册二次点击,所以我不知道还能做什么。
我也试过这个,但行为似乎是一样的:
BoardSquareView(
style: self.style(for: square),
model: square
)
.gesture(TapGesture().modifiers(.control).onEnded(self.handleUserDidAltTap(square)))
.onTapGesture(self.handleUserDidTap(square))
就目前 SwiftUI 的情况而言,这不可能直接实现。我相信它会在未来,但目前,TapGesture
显然主要集中在没有“右键单击”概念的 iOS 用例上,所以我认为这就是为什么它被忽略了。请注意,“长按”概念是 LongPressGesture
形式的第一个 class 公民,并且几乎专门用于支持该理论的 iOS 上下文。
就是说,我确实想出了一种方法来完成这项工作。您需要做的是退回到旧技术,并将其嵌入到您的 SwiftUI 视图中。
struct RightClickableSwiftUIView: NSViewRepresentable {
func updateNSView(_ nsView: RightClickableView, context: NSViewRepresentableContext<RightClickableSwiftUIView>) {
print("Update")
}
func makeNSView(context: Context) -> RightClickableView {
RightClickableView()
}
}
class RightClickableView: NSView {
override func mouseDown(with theEvent: NSEvent) {
print("left mouse")
}
override func rightMouseDown(with theEvent: NSEvent) {
print("right mouse")
}
}
我对此进行了测试,它在一个相当复杂的 SwiftUI 应用程序中对我有用。这里的基本做法是:
- 将您的听力组件创建为
NSView
。 - 用实现
NSViewRepresentable
. 的 SwiftUI 视图包装它
- 将你的实现放入你想要的 UI 中,就像你对任何其他 SwiftUI 视图所做的那样。
这不是一个理想的解决方案,但它现在可能已经足够好了。我希望这能解决您的问题,直到 Apple 进一步扩展 SwiftUI 的功能。
不幸接受的解决方案不适合我,因为点击 NSStatusItem.button
时没有视觉反馈。对我有用的是检测按钮触摸处理程序中的右键单击:
func setupStatusBarItem() {
statusBarItem.button?.action = #selector(didPressBarItem(_:))
statusBarItem.button?.target = self
statusBarItem.button?.sendAction(on: [.leftMouseDown, .rightMouseDown])
}
@objc func didPressBarItem(_ sender: AnyObject?) {
if let event = NSApp.currentEvent, event.isRightClick {
print("right")
} else {
print("left")
}
}
extension NSEvent {
var isRightClick: Bool {
let rightClick = (self.type == .rightMouseDown)
let controlClick = self.modifierFlags.contains(.control)
return rightClick || controlClick
}
}
灵感来自 article。
尽管我喜欢(并投赞成票)已接受的答案,但我找到了一种视图无需遵守 NSViewRepresentable
即可响应鼠标右键事件的方法。这种方法更干净地集成到我的应用程序中,因此可能值得考虑将其作为面临此问题的其他人的一种可能性。
我的解决方案首先是愿意接受在 macOS 中右键单击和控制左键单击传统上被视为等同的约定。此解决方案不允许以不同于控制左键单击的方式处理控制右键单击。但是任何其他修饰符都会被处理,因为它只会向它们添加 .control
并将其转换为左键单击。
如果您使用 SwiftUI 上下文菜单,可能会 破坏它们。我还没有测试过。
所以想法是使用控制键修饰符将鼠标右键事件转换为鼠标左键事件。
为了实现这一点,我对 NSHostingView
进行了子类化,并在 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)
}
}
然后在AppDelegate.didFinishLaunching
我改了
window.contentView = NSHostingView(rootView: contentView)
至
window.contentView = MyHostingView(rootView: contentView)
当然,必须对可能引用 NSHostingView
的任何其他代码进行类似的更改。通常 AppDelegate
中的引用是唯一的,但在一个重要的项目中可能还有其他引用。
鼠标右键事件随后在 SwiftUI 代码中显示为带有 .control
修饰符的 TapGesture
。
Text("Right-clickable Text")
.gesture(
TapGesture().modifiers(.control)
.onEnded
{ _ in
print("Control-Clicked")
}
)
这并不能完全解决扫雷器用例,但contextMenu
是一种在 SwiftUI macOS 应用程序中处理右键单击的方法。
例如:
.contextMenu {
Button(action: {}, label: { Label("Menu title", systemImage: "icon") })
}
将响应右键单击 macOS 和长按 iOS
将此添加到您的视图中。适用于 macOS
.contextMenu {
Button("Remove") {
print("remove this view")
}
}