swiftuimulti-touchuitapgesturerecognizerrootview

How to show sticker only when user double tap and using three fingers using SwiftUI?


I need to display Sticker Label only when I try to double tab with three fingers wherever into the app. I have tried using Multi-Touch solution. Seems I can see the sticker label but I can't able to access other actions like scrollable, touchable etc,..

Here below my code,

import SwiftUI

    // UIViewRepresentable for custom gesture recognition
struct GestureRecognizerView: UIViewRepresentable {
    var onDoubleTap: () -> Void
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(onDoubleTap: onDoubleTap)
    }
    
    class Coordinator: NSObject {
        var onDoubleTap: () -> Void
        var tapCount = 0
        var lastTapTime: TimeInterval = 0
        
        init(onDoubleTap: @escaping () -> Void) {
            self.onDoubleTap = onDoubleTap
        }
        
        @objc func handleGesture(_ gesture: UITapGestureRecognizer) {
            let currentTime = Date().timeIntervalSince1970
            if currentTime - lastTapTime < 0.3 {
                tapCount += 1
            } else {
                tapCount = 1
            }
            lastTapTime = currentTime
            
            if tapCount == 2 && gesture.numberOfTouches == 3 {
                onDoubleTap()
                tapCount = 0
            }
        }
    }
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        
        // Add UITapGestureRecognizer to detect double-tap with three fingers
        let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleGesture(_:)))
        tapGesture.numberOfTapsRequired = 2
        tapGesture.numberOfTouchesRequired = 3 // Three fingers
        
        view.addGestureRecognizer(tapGesture)
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // No update required
    }
}

If Use .Overlay then I can face the blocking issue. Yes I can see a transparent view. So expected Tab gesture working but remaining access doesn't work like scrollable, other touch events.

Then I tried with .background also this time I can access other actions like scrollable, other touch events but this time missing double tap with three finger detection to show sticker.

        .overlay(GestureRecognizerView {
            // Handle double-tap with three fingers
            withAnimation {
                showSticker.toggle() // Toggle sticker visibility
            }
        }
        )

May I know having any other way to achieve "Display Sticker label only when user doing double tap with three fingers?"

If anyone provide your valuable suggestions or solutions, It will be really appreciate.


Solution

  • I had a similar feature in an app where I wanted to trigger the tap gestures of all the views that overlap. I managed to find another answer that could help, although @jeep is saying that solution does not work very well with lots of nested views.

    Paul Hudson also has a relevant article on it too.

    Possible iOS 18+ solution

    Okay so we want a way to allow a tap to trigger the tap gestures of overlapping views — that’s what the above links are about.

    But now we just need a multi-tap gesture which is not supported in SwiftUI, but it is in UIKit gesture.

    So let’s convert the UIKit gesture to SwiftUI gesture, then use (although this is iOS 18+).

    Here is the code I came up with:

    import SwiftUI
    
    struct ContentView: View {
        @State private var showSticker = false
        
        var body: some View {
            ZStack {
                List(0..<100) { index in
                    Text("Item \(index)")
                        .padding()
                        .background(Color.gray.opacity(0.2))
                }
                
                if showSticker {
                    Text("Sticker Label")
                        .font(.largeTitle)
                        .padding()
                        .background(Color.yellow)
                        .cornerRadius(8)
                        .transition(.opacity)
                }
            }
            .gesture(MultitouchGesture(onTap: { // iOS 18+
                showSticker.toggle()
            }))
        }
    }
    
    
    fileprivate struct MultitouchGesture: UIGestureRecognizerRepresentable {
        // Inputs
        let onTap: () -> ()
        var numFingers = 3
        var numTaps = 2
        
        // Create a coordianator that is in charge of running the onTap method.
        // So that we can use @objc here
        class Coordinator: NSObject {
            private let onTap: () -> ()
            init(onTap: @escaping () -> Void) {
                self.onTap = onTap
            }
            
            @objc func handleTouch() {
                onTap()
            }
        }
        
        func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
            Coordinator(onTap: onTap)
        }
        
        // Convert UIKit gestures to SwiftUI
        func makeUIGestureRecognizer(context: Context) -> UITapGestureRecognizer {
            let tapGesture = UITapGestureRecognizer(
                target: context.coordinator,
                action: #selector(context.coordinator.handleTouch)
            )
            tapGesture.numberOfTapsRequired = numTaps
            tapGesture.numberOfTouchesRequired = numFingers
            
            return tapGesture
        }
    }
    
    
    #Preview {
        ContentView()
    }
    

    In that code:

    Hopefully my 1.5hr of solving this problem helps!