Nikita Zernov
Nikita Zernov

Reputation: 5645

Handling both double and triple gesture recognisers in SwiftUI

I am trying to distinguish double and triple tap gesture recognisers in SwiftUI. In storyboard-based application it can be done using UIGestureRecognizerDelegate, but in SwiftUI I haven't found any solutions.

NOTE:

The following doesn't work:

.onTapGesture(count: 3) {
    print("Triple Tap!")
}
.onTapGesture(count: 2) {
    print("Double Tap!")
}

Upvotes: 9

Views: 2345

Answers (4)

Mahdi BM
Mahdi BM

Reputation: 2104

This is the right way, if i may say so myself!

Tested on iOS 14.4.2, Xcode 12.5 beta 3.

Using this tapGesture, after only one tap nothing happens, after double tap you'll see "double tap" printed in console, and after triple or more tap you'll see "triple tap" printed in console.

struct ContentView: View {
    var tapGesture: some Gesture {
        SimultaneousGesture(TapGesture(count: 2), TapGesture(count: 3))
            .onEnded { gestureValue in
                if gestureValue.second != nil {
                    print("triple tap!")
                } else if gestureValue.first != nil {
                    print("double tap!")
                }
            }
    }
    
    var body: some View {
        Text("Hello World!")
            .gesture(tapGesture)
    }
}

Upvotes: 8

swiftPunk
swiftPunk

Reputation: 1

To having some multi gesture that fits every ones needs in projects, Apple has nothing offer than normal gesture, mixing them together to reach the wished gesture some times get tricky, here is a salvation, working without issue or bug!

A custom zero issue gesture called tapCountRecognizer, we can apply it to any View. For having single and double and triple tapGesture in the same time working together.

Notice that you can use like this as well, Just for doubleTap and tripleTap:

.tapCountRecognizer(tapSensitivity: 250, doubleTapAction: doubleTapAction, tripleTapAction: tripleTapAction)

Or:

.tapCountRecognizer(doubleTapAction: doubleTapAction, tripleTapAction: tripleTapAction)

enter image description here


import SwiftUI

struct ContentView: View {
    
    var body: some View {
        
        Circle()
            .fill(Color.red)
            .frame(width: 100, height: 100, alignment: .center)
            .tapCountRecognizer(tapSensitivity: 250, singleTapAction: singleTapAction, doubleTapAction: doubleTapAction, tripleTapAction: tripleTapAction)
            .shadow(radius: 10)
            .statusBar(hidden: true)
        
    }
    
    func singleTapAction() { print("singleTapAction") }
    
    func doubleTapAction() { print("doubleTapAction") }
    
    func tripleTapAction() { print("tripleTapAction") }
    
}

struct TapCountRecognizerModifier: ViewModifier {
    
    let tapSensitivity: Int
    let singleTapAction: (() -> Void)?
    let doubleTapAction: (() -> Void)?
    let tripleTapAction: (() -> Void)?
    
    
    init(tapSensitivity: Int = 250, singleTapAction: (() -> Void)? = nil, doubleTapAction: (() -> Void)? = nil, tripleTapAction: (() -> Void)? = nil) {
        
        self.tapSensitivity  = ((tapSensitivity >= 0) ? tapSensitivity : 250)
        self.singleTapAction = singleTapAction
        self.doubleTapAction = doubleTapAction
        self.tripleTapAction = tripleTapAction
        
    }
    
    @State private var tapCount: Int = Int()
    @State private var currentDispatchTimeID: DispatchTime = DispatchTime.now()
    
    func body(content: Content) -> some View {

        return content
            .gesture(fundamentalGesture)
        
    }
    
    var fundamentalGesture: some Gesture {
        
        DragGesture(minimumDistance: 0.0, coordinateSpace: .local)
            .onEnded() { _ in tapCount += 1; tapAnalyzerFunction() }
        
    }
    
    
    
    func tapAnalyzerFunction() {
        
        currentDispatchTimeID = dispatchTimeIdGenerator(deadline: tapSensitivity)
        
        if tapCount == 1 {
            
            let singleTapGestureDispatchTimeID: DispatchTime = currentDispatchTimeID
            
            DispatchQueue.main.asyncAfter(deadline: singleTapGestureDispatchTimeID) {

                if (singleTapGestureDispatchTimeID == currentDispatchTimeID) {

                    if let unwrappedSingleTapAction: () -> Void = singleTapAction { unwrappedSingleTapAction() }

                    tapCount = 0
                    
                }
                
            }
            
        }
        else if tapCount == 2 {
            
            let doubleTapGestureDispatchTimeID: DispatchTime = currentDispatchTimeID
            
            DispatchQueue.main.asyncAfter(deadline: doubleTapGestureDispatchTimeID) {
                
                if (doubleTapGestureDispatchTimeID == currentDispatchTimeID) {
 
                    if let unwrappedDoubleTapAction: () -> Void = doubleTapAction { unwrappedDoubleTapAction() }
                    
                    tapCount = 0
                    
                }
                
            }
            
        }
        else  {
            
            
            if let unwrappedTripleTapAction: () -> Void = tripleTapAction { unwrappedTripleTapAction() }
            
            tapCount = 0
            
        }
        
    }
    
    func dispatchTimeIdGenerator(deadline: Int) -> DispatchTime { return DispatchTime.now() + DispatchTimeInterval.milliseconds(deadline) }
    
}

extension View {
    
    func tapCountRecognizer(tapSensitivity: Int = 250, singleTapAction: (() -> Void)? = nil, doubleTapAction: (() -> Void)? = nil, tripleTapAction: (() -> Void)? = nil) -> some View {
        
        return self.modifier(TapCountRecognizerModifier(tapSensitivity: tapSensitivity, singleTapAction: singleTapAction, doubleTapAction: doubleTapAction, tripleTapAction: tripleTapAction))
        
    }
    
}

Upvotes: 5

Higherous
Higherous

Reputation: 102

For this, you can use .gesture with .simultaneously(with:). From the documentation: Combines a gesture with another gesture to create a new gesture that recognizes both gestures at the same time. And example code is

struct TwoGesturesView: View {
    var body: some View {
        Text("Hello World!")
            .gesture(TapGesture(count: 2).onEnded({
                print("Double Tap")
            })
            .simultaneously(with: TapGesture(count: 3).onEnded({
                print("Triple Tap")
            })))
        
    }
}

Upvotes: 0

lorem ipsum
lorem ipsum

Reputation: 29676

import SwiftUI

struct MultipleTaps: View {
    let doubleTap = TapGesture(count: 2).onEnded({print("Double Tap!")})
    let tripleTap = TapGesture(count: 3).onEnded({print("Triple Tap!")})
    var body: some View {
        let tripleBeforedouble = tripleTap.exclusively(before: doubleTap)

        return Text("Hello World!").gesture(tripleBeforedouble)
    }
}

struct MultipleTaps_Previews: PreviewProvider {
    static var previews: some View {
        MultipleTaps()
    }
}

Upvotes: 1

Related Questions