iosswiftuigesturedurationlong-press

SwiftUI LongPressGesture takes too long to recognize when TapGesture also present


I would like to recognize a TapGesture and LongPressGesture on the same item. And it works fine, with the following exception: the LongPressGesture alone responds after the duration I specify, which is 0.25 seconds, but when I combine it with the TapGesture, it takes at least 1 second—I can't find a way to make it respond more quickly. Here is a demo:

enter image description here

And here is the code for it:

struct ContentView: View {
    @State var message = ""

    var body: some View {
        Circle()
            .fill(Color.yellow)
            .frame(width: 150, height: 150)
            .onTapGesture(count: 1) {
                message = "TAP"
            }
            .onLongPressGesture(minimumDuration: 0.25) {
                message = "LONG\nPRESS"
            }
            .overlay(Text(message)
                        .font(.title).bold()
                        .multilineTextAlignment(.center)
                        .allowsHitTesting(false))
    }
}

Notice that it works fine except for the duration of the LongPress, which is much longer than 0.25 seconds.

Any ideas? Thanks in advance!


Solution

  • 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!

    Here a custom zero issue gesture called interactionReader, we can apply it to any View. for having LongPressGesture and TapGesture in the same time.


    enter image description here


    import SwiftUI
    
    struct ContentView: View {
        
        var body: some View {
            
            Circle()
                .fill(Color.yellow)
                .frame(width: 150, height: 150)
                .interactionReader(longPressSensitivity: 250, tapAction: tapAction, longPressAction: longPressAction, scaleEffect: true)
                .animation(Animation.easeInOut(duration: 0.2))
            
        }
        
        func tapAction() { print("tap action!") }
        
        func longPressAction() { print("longPress action!") }
        
    }
    

    struct InteractionReaderViewModifier: ViewModifier {
        
        var longPressSensitivity: Int
        var tapAction: () -> Void
        var longPressAction: () -> Void
        var scaleEffect: Bool = true
        
        @State private var isPressing: Bool = Bool()
        @State private var currentDismissId: DispatchTime = DispatchTime.now()
        @State private var lastInteractionKind: String = String()
        
        func body(content: Content) -> some View {
            
            let processedContent = content
                .gesture(gesture)
                .onChange(of: isPressing) { newValue in
                    
                    currentDismissId = DispatchTime.now() + .milliseconds(longPressSensitivity)
                    let dismissId: DispatchTime = currentDismissId
                    
                    if isPressing {
                        
                        DispatchQueue.main.asyncAfter(deadline: dismissId) {
                            
                            if isPressing { if (dismissId == currentDismissId) { lastInteractionKind = "longPress"; longPressAction() } }
                            
                        }
                        
                    }
                    else {
                        
                        if (lastInteractionKind != "longPress") { lastInteractionKind = "tap"; tapAction() }
                        
                        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {lastInteractionKind = "none"}
                        
                        
                    }
                    
                }
            
            return Group {
                
                if scaleEffect { processedContent.scaleEffect(lastInteractionKind == "longPress" ? 1.5: (lastInteractionKind == "tap" ? 0.8 : 1.0 )) }
                else { processedContent }
                
            }
    
        }
        
        var gesture: some Gesture {
            
            DragGesture(minimumDistance: 0.0, coordinateSpace: .local)
                .onChanged() { _ in if !isPressing { isPressing = true } }
                .onEnded() { _ in isPressing = false }
            
        }
        
    }
    

    extension View {
        
        func interactionReader(longPressSensitivity: Int, tapAction: @escaping () -> Void, longPressAction: @escaping () -> Void, scaleEffect: Bool = true) -> some View {
            
            return self.modifier(InteractionReaderViewModifier(longPressSensitivity: longPressSensitivity, tapAction: tapAction, longPressAction: longPressAction, scaleEffect: scaleEffect))
            
        }
        
    }