简体   繁体   中英

SwiftUI: Multitouch gesture / Multiple Gestures

Is there a way in SwiftUI to track multiple gestures at once? I want my one main view to be able to keep track of multiple fingers dragging at once.

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

I have this code however I can only ever have one drag gesture being processed at a time. If one finger is dragging and then I try to add another one the first one stops.

I am trying to achieve an effect where multiple fingers are on screen at once. Each finger is dragging a circle simultaneously (one circle is following each finger).

I see simultaneous gestures on Apple's documentation, but this is referring to have one gesture trigger multiple blocks.

the solution ( How to detect a tap gesture location in SwiftUI? ) that uses UIViewRepresentable would work. Although its not the pure swiftUI solution (I've looked for a pure swiftUI solution but cannot find one and would be interested to see one). I've updated code to the below code, you would need to changed the numberOfTouchPoints = 5. and then use call back to get value into view.

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) {
        self.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))
        }
    }
}

I combined Paul's answer with the outdated example from Apple to create a way to detect as many touches as you like.

import Foundation
import UIKit

class NFingerGestureRecognizer: UIGestureRecognizer {

    var touchViews = [UITouch:CGPoint]()

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

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

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

}

Update:

You need to wrap this into an UIViewRepresentable to use it as a SwiftUI component:

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
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM