在swiftUI中捕获onLongPressGesture的touchDown位置?

Posted

技术标签:

【中文标题】在swiftUI中捕获onLongPressGesture的touchDown位置?【英文标题】:Capture touchDown location of onLongPressGesture in swiftUI? 【发布时间】:2020-07-10 15:40:05 【问题描述】:

我正在尝试实现一个自定义上下文菜单,该菜单将在长按用户触摸的位置后出现。我一直无法找到一种方法来捕获 onLongPressGesture 的触地事件的 XY 位置。

这就是我开始的地方

struct ExampleView: View 
    @State var showCustomContextMenu = false
    @State var longPressLocation = CGPoint.zero
    
    var body: some View 
        Rectangle()
            .foregroundColor(Color.green)
            .frame(width: 100.0, height: 100.0)
            .onLongPressGesture 
                print("OnLongPressGesture")
                self.showCustomContextMenu = true
            
            .overlay(
                Rectangle()
                    .foregroundColor(Color.red)
                    .frame(width: 50.0, height: 50.0)
                    .position(longPressLocation) // <----- this is what I need to capture.
                    .opacity( (showCustomContextMenu) ? 1 : 0 )
        )
    

在查看了这个问题(以及答案中链接的其他 SO 问题)后,我尝试了以下操作。

How do you detect a SwiftUI touchDown event with no movement or duration?

struct ExampleView: View 
    @State var showCustomContextMenu = false
    @State var longPressLocation = CGPoint.zero
    
    var body: some View 
        ZStack
            Rectangle()
                .foregroundColor(Color.green)
                .frame(width: 100.0, height: 100.0)
                .onLongPressGesture 
                    print("OnLongPressGesture")
                    self.showCustomContextMenu = true
                
                .overlay(
                    Rectangle()
                        .foregroundColor(Color.red)
                        .frame(width: 50.0, height: 50.0)
                        .position(longPressLocation)
                        .opacity( (showCustomContextMenu) ? 1 : 0 )
            )
            TapView  point in
                self.longPressLocation = point
                print("Point: \(point)")
            .background(Color.gray).opacity(0.5)
        
    


struct TapView: UIViewRepresentable 
    var tappedCallback: ((CGPoint) -> Void)

    func makeUIView(context: UIViewRepresentableContext<TapView>) -> TapView.UIViewType 
        let v = UIView(frame: .zero)
        let gesture = SingleTouchDownGestureRecognizer(target: context.coordinator,
                                                       action: #selector(Coordinator.tapped))
        v.addGestureRecognizer(gesture)
        return v
    

    class Coordinator: NSObject 
        var tappedCallback: ((CGPoint) -> Void)

        init(tappedCallback: @escaping ((CGPoint) -> Void)) 
            self.tappedCallback = tappedCallback
        

        @objc func tapped(gesture:UITapGestureRecognizer) 
            self.tappedCallback(gesture.location(in: gesture.view))
        
    

    func makeCoordinator() -> TapView.Coordinator 
        return Coordinator(tappedCallback:self.tappedCallback)
    

    func updateUIView(_ uiView: UIView,
                      context: UIViewRepresentableContext<TapView>) 
    


class SingleTouchDownGestureRecognizer: UIGestureRecognizer 
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) 
        if self.state == .possible 
            self.state = .recognized
        
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) 
        self.state = .failed
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) 
        self.state = .failed
    

这几乎可行,但是,这给我留下的问题是,由于 Rectangle() 和 TapView() 位于 ZStack 中,这取决于我将它们放置在代码中的位置 either touchDown 位置 onLongPressGesture,但不能同时使用。

我查看过但遇到类似问题的其他 SO 问题链接如下

How to detect a tap gesture location in SwiftUI?

Swift: Long Press Gesture Recognizer - Detect taps and Long Press 这可能是我正在寻找的,但我不确定如何使其适应 SwiftUI。

【问题讨论】:

【参考方案1】:

这是一个可能的方法的演示。它需要两种手势的组合:长按检测长按和拖动检测位置。

使用 Xcode 12 / ios 14 测试。(在以下系统上,可能需要将 self. 添加到某些属性使用中)

struct ExampleView: View 
    @State var showCustomContextMenu = false
    @State var longPressLocation = CGPoint.zero

    var body: some View 
        Rectangle()
            .foregroundColor(Color.green)
            .frame(width: 100.0, height: 100.0)
            .onTapGesture  showCustomContextMenu = false  // just for demo
            .gesture(LongPressGesture(minimumDuration: 1).sequenced(before: DragGesture(minimumDistance: 0, coordinateSpace: .local))
                .onEnded  value in
                    switch value 
                        case .second(true, let drag):
                            longPressLocation = drag?.location ?? .zero   // capture location !!
                            showCustomContextMenu = true
                        default:
                            break
                    
            )
            .overlay(
                Rectangle()
                    .foregroundColor(Color.red)
                    .frame(width: 50.0, height: 50.0)
                    .position(longPressLocation)
                    .opacity( (showCustomContextMenu) ? 1 : 0 )
                    .allowsHitTesting(false)
        )
    

【讨论】:

由于某种原因,它只适用于画布预览。在模拟器中,当我长按时,位置是.zero,所以 Rectangle() 显示在 .zero CGPoint

以上是关于在swiftUI中捕获onLongPressGesture的touchDown位置?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI捕获键盘提交动作在iOS15之前和之后的兼容实现

在 SwiftUI 中使用 Combine 进行映射、捕获错误和分配

在 SwiftUI 中捕获文本字段值而不按返回键 [重复]

SwiftUI 从另一个视图中捕获 Picker 值

SwiftUI iOS - 如何使用捕获的硬件按键事件

SwiftUI 转义闭包捕获变异的“自我”参数