如何检测 SwiftUI 中的右键单击?

Posted

技术标签:

【中文标题】如何检测 SwiftUI 中的右键单击?【英文标题】:How can I detect a right-click in SwiftUI? 【发布时间】:2020-04-04 09:49:09 【问题描述】:

我写信 a simple Mines app 是为了帮助我了解 SwiftUI。因此,我希望主要点击(通常是 LMB)来“挖掘”(显示那里是否有地雷),以及二次点击(通常是 RMB)来放置标志。

我有挖掘工作!但我不知道如何放置标志,因为我不知道如何检测二次点击。

Here's what I'm trying:

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))

【问题讨论】:

您的第一个链接已损坏。私人回购? .onTapGesture() 看看吧。 哎呀,你是对的@GilBirman!固定的;对此感到抱歉 @Raymond 我先试过了。除非我遗漏了一些重要的东西,否则它的行为似乎与 .gesture(TapGesture().onEnded(.......)) 【参考方案1】:

就目前的 SwiftUI 而言,这不可能直接实现。我相信它会在未来出现,但目前,TapGesture 显然主要关注没有“右键单击”概念的 ios 用例,所以我认为这就是为什么它被忽略了.请注意,“长按”概念是 LongPressGesture 形式的一等公民,并且几乎专门用于支持该理论的 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 的功能。

【讨论】:

谢谢。这基本上是what I was doing for now。真的很伤心,这是他们对框架的第一印象。我会将其标记为已接受,直到(希望??)有更好的解决方案出现 求助;看起来这是今天最好的答案。赏金是你的!希望有一天我们会有一个正式的解决方案。 @B.T.这仍然是推荐的解决方案还是 SwiftUI 2.0 带来了一些改进? @mica 查看官方文档,他们似乎没有为任何以鼠标样式交互表示的手势添加本机支持。它们都以“点击”和手指式交互的形式表达,这意味着很难从这种哲学中获得右键单击。文档在这里:developer.apple.com/documentation/swiftui/gestures 在这里:developer.apple.com/documentation/swiftui/eventmodifiers @B.T.你如何将 RightClickableSwiftUIView 应用到 Text() 到其他 SwiftUI-View?【参考方案2】:

虽然我喜欢(并赞成)接受的答案,但我找到了一种方法来响应鼠标右键事件,而不必符合 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")
                        
                )

【讨论】:

谢谢。顺便说一句 - 我学到了一个艰难的方法,如果你有多个 .gesture 调用附加到同一个 View,你想把带有修饰符的那些放在第一位。我有一个首先包含一个未修改的手势,它总是被调用的那个。交换订单解决了它。【参考方案3】:

不幸的是,被接受的解决方案不适合我,因为单击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。

【讨论】:

【参考方案4】:

这并不能精确解决扫雷用例,但contextMenu 是在 SwiftUI macOS 应用程序中处理右键单击的一种方法。

例如:

.contextMenu 
    Button(action: , label:  Label("Menu title", systemImage: "icon") )

将响应在 macOS 上的右键单击和在 iOS 上的长按

【讨论】:

以上是关于如何检测 SwiftUI 中的右键单击?的主要内容,如果未能解决你的问题,请参考以下文章

显示 QMenu 以响应 QLabel 中的右键单击,可能吗?

java - 如何获取Java Swing列表中未选择的右键单击项目?

opencv中的右键事件

在浏览器 DevTools 中不需要的右键单击

vs代码中的右键上下文菜单立即执行

利用jQuery禁止页面的右键单击