SwiftUI: 多点触控手势 / 多个手势

15

在SwiftUI中,有没有一种方法可以同时跟踪多个手势?我希望我的一个主视图能够同时跟踪多个手指的拖动。

ZStack {
    Color.black
      .edgesIgnoringSafeArea(.all)
      .gesture(DragGesture(minimumDistance: 0)
               .onChanged { (value) in
                 //some logic
               }.onEnded { (value) in
                  //more logic         
               })
       //other code
}

我有这段代码,但是一次只能处理一个拖动手势。如果一个手指正在拖动,然后我尝试添加另一个手指,第一个手指会停止。

我正在尝试实现这样的效果:多个手指同时在屏幕上。每个手指都同时拖动一个圆形(每个手指都有一个圆形跟随)。

我在苹果的文档中看到了simultaneous gestures,但这指的是一个手势触发多个块。


你解决了吗?我也遇到了同样的问题。 - SandeepAggarwal
我还没有尝试过这个解决方案,但它似乎不可行。 - Matt Bart
我也还没有查看SwiftUI 2.0,也许那里有解决方案。 - Matt Bart
2个回答

2

使用UIViewRepresentable的解决方案(如何在SwiftUI中检测轻击手势位置?)会起作用。虽然它不是纯的swiftUI解决方案(我已经寻找了一个纯的swiftUI解决方案,但找不到,并且有兴趣看到一个)。我已将代码更新为下面的代码,您需要更改numberOfTouchPoints = 5。然后使用回调将值传递给视图。

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

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

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

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

        @objc func tapped(gesture:NFingerGestureRecognizer) {
            for i in 0..<gesture.numberOfTouches{
                print(gesture.location(ofTouch: i, in: gesture. view))
            }
            self.tappedCallback()
        }
    }

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

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

class NFingerGestureRecognizer: UIGestureRecognizer {
    var numberOfTouchPoints: Int = 2
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            if (self.numberOfTouches == numberOfTouchPoints){ // we have a two finger interaction starting
                self.state = .began
                print("two finger drag - Start")
            }
        } else {        // check to see if there are more touchs
            if (self.numberOfTouches > numberOfTouchPoints){ // we have a two finger interaction starting
                self.state = .failed
                print("two finger drag - failed")
            }
        }
        //printInternalTouches()
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        if (self.state != .possible){
            self.state = .changed        // we are looking for two finger interaction
            //printInternalTouches()
            print("changed")
        }
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        if numberOfTouches == 2 {
            state = .ended
        }
        else {
            state = .failed
        }
        //printInternalTouches()
        print("ended")
    }
    
    private func printInternalTouches(){
        // now print the internal touchUI elements - should be the better solution
        print("Internal Locations")
        for i in 0..<self.numberOfTouches{
            print(self.location(ofTouch: i, in: self.view))
        }
    }
}

如何使用回调将值传递到视图中?您能否提供一个使用此代码的示例。 - Eugen Soloviov
1
@suenot 我已经有一段时间没有看过这个代码了。基本思路是你有一个 'var body: some View { TapView(tappedCallback: theTappedCallback}',然后在视图内部有回调函数。 实际上我创建了三个事件,开始拖动、拖动变化、结束拖动。这些与上面代码中的打印语句相关。希望这可以帮到你。 - Paul Cardno

1
我将保留Paul的答案和Apple过时的示例,以创建一种可以检测尽可能多的触摸的方法。
更新3:(注释代码用于调试输出)
import Foundation
import UIKit

class NFingerGestureRecognizer: UIGestureRecognizer {

    var tappedCallback: (UITouch, CGPoint?) -> Void

    var touchViews = [UITouch:CGPoint]()

    init(target: Any?, tappedCallback: @escaping (UITouch, CGPoint?) -> ()) {
        self.tappedCallback = tappedCallback
        super.init(target: target, action: nil)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        for touch in touches {
            let location = touch.location(in: touch.view)
            // print("Start: (\(location.x)/\(location.y))")
            touchViews[touch] = location
            tappedCallback(touch, location)
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        for touch in touches {
            let newLocation = touch.location(in: touch.view)
            // let oldLocation = touchViews[touch]!
            // print("Move: (\(oldLocation.x)/\(oldLocation.y)) -> (\(newLocation.x)/\(newLocation.y))")
            touchViews[touch] = newLocation
            tappedCallback(touch, newLocation)
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        for touch in touches {
            // let oldLocation = touchViews[touch]!
            // print("End: (\(oldLocation.x)/\(oldLocation.y))")
            touchViews.removeValue(forKey: touch)
            tappedCallback(touch, nil)
        }
    }

}

更新:

您需要将此内容包装到 UIViewRepresentable 中,以便将其用作 SwiftUI 组件:

import Foundation
import SwiftUI

struct TapView: UIViewRepresentable {

    var tappedCallback: (UITouch, CGPoint?) -> Void

    func makeUIView(context: UIViewRepresentableContext<TapView>) -> TapView.UIViewType {
        let v = UIView(frame: .zero)
        let gesture = NFingerGestureRecognizer(target: context.coordinator, tappedCallback: tappedCallback)
        v.addGestureRecognizer(gesture)
        return v
    }
    
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<TapView>) {
        // empty
    }

}

更新2:

以下是在SwiftUI中如何使用TapView的简单示例:

var body: some View {
    guard let img = UIImage(named: "background.png") else {
        fatalError("Unable to load image")
    }
    return GeometryReader { geometry in
        ZStack {
            Image(uiImage: img)
                    .resizable()
                    .aspectRatio(geometry.size, contentMode: .fill)
                    .edgesIgnoringSafeArea(.all)


            TapView { touch, optLocation in
                // touch can be used as a dictionary key.
                // optLocation is nil when the touch ends.
            }
        }
    }
}

这在SwiftUI中如何使用? - Ronnie
@FLUXparticle 在字符串let gesture = NFingerGestureRecognizer(target: context.coordinator, tappedCallback: tappedCallback)中,我有两个错误:1. 无法将类型为'(UITouch, CGPoint?) -> Void'的值转换为预期的参数类型'Selector?',2. 调用中的参数标签不正确(应为'target:action:',而不是'target:tappedCallback:') - Eugen Soloviov
@suenot 看起来我忘记复制初始化代码了,抱歉…现在应该可以工作了。 - FLUXparticle
1
@suenot 我添加了一个例子... 希望能帮到你... - FLUXparticle
tappedCallback 从未被调用。 - Alexander Volkov
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接