【问题标题】:SwiftUI LongPressGesture takes too long to recognize when TapGesture also presentSwiftUI LongPressGesture 需要很长时间才能识别 TapGesture 何时也存在
【发布时间】:2021-06-24 21:28:54
【问题描述】:

我想识别同一商品上的 TapGestureLongPressGesture。它工作正常,除了以下例外:LongPressGesture 单独在我指定的持续时间(0.25 秒)后响应,但是当我将它与TapGesture 结合使用时,至少需要 1 秒——我找不到一种让它更快响应的方法。这是一个演示:

这是它的代码:

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

请注意,除了 LongPress 的持续时间长于 0.25 秒之外,它工作正常。

有什么想法吗?提前致谢!

【问题讨论】:

    标签: ios swiftui gesture duration long-press


    【解决方案1】:

    为了在项目中拥有一些适合每个人需要的多手势,Apple 提供的只是普通手势,将它们混合在一起以达到想要的手势有时会变得很棘手,这是一种拯救,没有 问题或错误!

    这里有一个名为 interactionReader 的自定义 zero issue 手势,我们可以将其应用于任何 View。 LongPressGestureTapGesture 同时



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

    【讨论】:

    • 谢谢你,@swiftPunk!您提供的解决方案几乎可以工作。不幸的是,在我 LongPress 的大约 20% 的时间里,它被注册为 LongPress,然后是 Tap。此外,冒着“移动球门柱”的风险,我最初的任务除了 LongPress 之外还需要单击和双击。在我的代码中添加双击很简单(2 个 .onTapGesture 修饰符代替 1)并且问题是相同的:除了 LongPress 的持续时间外,它按预期工作。所以我没有提到双击,以保持我的例子简单。这两种水龙头似乎会使您的解决方案复杂化,不是吗?
    • 带有手势,这就是我们可以通过Apple内置获得的,否则您将开始深入研究并根据需要进行自定义手势,因此在此答案中的重点是使两者单独工作为了更特别的救赎,我会请你提出另一个问题,并给出创建问题的代码。谢谢
    • 如果我能找到我发布的问题的解决方案并且始终有效,我会看看它是否也适用于多次点击 - 如果没有,也许我会像你说的那样做一个单独的帖子.目前,为这个问题找到解决方案将是向前迈出的一步。 (在我解决了这个更简单的问题之前,我不希望解决更困难的问题。)谢谢。
    【解决方案2】:

    嗯,这不是很漂亮,但效果很好。它记录每次点击/按下的开始,如果它在 0.25 秒之前结束,它认为它是 TapGesture,否则它认为它是 LongPressGesture

    struct ContentView: View {
        @State var pressInProgress = false
        @State var gestureEnded = false
        @State var workItem: DispatchWorkItem? = nil
        @State var message = ""
    
        var body: some View {
    
            Circle()
                .fill(Color.yellow)
                .frame(width: 150, height: 150, alignment: .center)
                .overlay(Text(message)
                             .font(.title).bold()
                             .multilineTextAlignment(.center)
                             .allowsHitTesting(false))
                .gesture(
                    DragGesture(minimumDistance: 0, coordinateSpace: .global)
                        .onChanged { _ in
                            guard !pressInProgress else { return }
                            pressInProgress = true
                            workItem = DispatchWorkItem {
                                message = "LONG\nPRESSED"
                                gestureEnded = true
                            }
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: workItem!)
                    }
                    .onEnded { _ in
                        pressInProgress = false
                        workItem?.cancel()
                        guard !gestureEnded else { // moot if we're past 0.25 seconds so ignore
                            gestureEnded = false
                            return
                        }
                        message = "TAPPED"
                        gestureEnded = false
                    }
            )
        }
    }
    

    如果有人能想到一个实际使用 LongPressGestureTapGesture 的解决方案,我会更喜欢这个!

    【讨论】:

    • 你说如果有人能想到一个真正使用 LongPressGesture 和 TapGesture 的解决方案,我会更喜欢那个!,事实上它们都来自苹果制造的 DragGesture来自 DragGesture 的那个简单的 TapGesture,看起来很疯狂,但事实就是如此。当您将它们混合在一起时,请记住我告诉过您我们应该使用自定义手势,在我回答您的问题时,我基本上做了一个新手势。这就是为什么它可以按我们想要的方式工作。我将这个手势命名为 interactionReader,你可以将 interactionReader 应用到任何 View,它适用于 tap/LongPressGesture
    • 感谢您的上述解决方案,swiftPunk - 非常感谢!需要明确的是,我正在寻找的不是自定义手势。相反,它是标准 Apple 手势的一种实现,因此查找多个手势并不麻烦。一般来说,Apple 的手势是可组合的,只要 LongPressGesture 的参数为 1 秒或更长,组合这些手势就可以正常工作……但无法指定 LongPressGesture 是否被赋予更短的持续时间。
    【解决方案3】:

    LongPressGesture 有另一个完成,可以在用户第一次触地时执行一个动作......并在最短持续时间再次执行。像这样的东西可以吗?

    struct LongTapTest: View {
        @State var message = ""
    
        var body: some View {
            Circle()
                .fill(Color.yellow)
                .frame(width: 150, height: 150)
                .onLongPressGesture(minimumDuration: 0.25, maximumDistance: 50, pressing: { (isPressing) in
                if isPressing {
                    // called on touch down
                } else {
                    // called on touch up
                    message = "TAP"
                }
            }, perform: {
                message = "LONG\nPRESS"
            })
                .overlay(Text(message)
                            .font(.title).bold()
                            .multilineTextAlignment(.center)
                            .allowsHitTesting(false))
        }
    }
    

    【讨论】:

    • 谢谢,@nicksarno。在我看来,这将在点击/按下初始化时触发一次,然后在/如果按下持续 0.25 秒时再次触发。追求的路线不错,但解决不了问题;需要的是首先记录发生了点击/按下事件,然后根据按下的结束时间来决定是点击还是长按。如果开始后不到 0.25 秒,则为轻击;否则它是一个长按。事实上,这给了我一个解决方案的想法,我将很快发布。谢谢!
    • @Anton 我编辑了我认为可能对你有用的答案。 isPressing 被调用两次,一次是用户第一次触摸时,另一次是用户抬起手指时。您可以在手指抬起时执行“TAP”的逻辑(当 isPressing 为假时)。
    • 啊,太棒了 - 我从来没有注意到压紧的关闭。通过您所做的更改,效果很好。谢谢,@nicksarno!
    • 当然,尼克。只是想先对其进行实际测试,以确保我没有遗漏任何东西! :)
    • 不幸的是,我测试了它,这也不太有效。在成功的长按检测中,按压和执行关闭都会触发。不过,可以通过添加 State 变量来修复。
    猜你喜欢
    • 2013-10-11
    • 2012-12-04
    • 2012-12-03
    • 2014-07-21
    • 2019-10-26
    • 2012-06-24
    • 2013-11-15
    • 2014-02-23
    • 2011-03-14
    相关资源
    最近更新 更多