You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI:触摸缩放圆形,轻触后持续缩放的问题排查与修复

问题描述

需求:实现一个圆形,用户按住时缓慢缩小,在两种情况停止并重置缩放至1:

  1. 检测到LongPressGesture触发(长按完成)
  2. 手势结束(手指抬起)

现有代码长按功能正常,但轻触时圆形会持续缩小且不停止。控制台输出如下:

  • 轻触仅打印一次changed,无ended输出
  • 长按正常打印:
changed
ended

现有代码:

struct ContentView: View {
    
    @State private var longPressTimer: Timer?
    @State private var longPressDuration: TimeInterval = 0
    
    private var gesture: some Gesture {
        LongPressGesture()
            .onChanged { isPressing in
                print("changed")
                if isPressing {
                    longPressTimer = Timer.scheduledTimer(withTimeInterval: 1/25, repeats: true) { timer in
                        longPressDuration += timer.timeInterval
                    }
                } else {
                    reset()
                }
            }
            .onEnded { _ in
                print("ended")
                reset()
            }
    }
    
    var body: some View {
        Circle()
            .foregroundColor(.red)
            .scaleEffect(1 - longPressDuration/5)
            .gesture(gesture)
    }
    
    private func reset() {
        longPressDuration = 0
        longPressTimer?.invalidate()
        longPressTimer = nil
    }
}
原因分析
  • LongPressGesture 仅在按压时长达到系统默认阈值(约0.5秒)时,才会触发 onEnded 回调;轻触时按压时间不足,手势不会完成,因此不会调用 onEnded。
  • LongPressGesture 的 onChanged 仅在开始按压时触发一次(参数isPressing为true),轻触抬起时不会触发onChanged(false)——因为手势从未进入“长按触发”状态,没有状态反转的回调。
  • 这导致轻触时计时器被创建并持续运行,但没有任何逻辑去停止它,圆形也就一直缩小。
修复方法

替换原有手势为组合手势:用DragGesture捕捉所有按压/抬起事件,同时用LongPressGesture检测长按完成,确保两种场景都能触发重置:

struct ContentView: View {
    
    @State private var longPressTimer: Timer?
    @State private var longPressDuration: TimeInterval = 0
    @State private var didTriggerLongPress = false // 标记是否已触发长按完成
    
    private var gesture: some Gesture {
        // 捕捉按压/抬起的DragGesture
        DragGesture(minimumDistance: 0)
            .onChanged { _ in
                // 未触发过长按时才启动计时器
                if !didTriggerLongPress {
                    startTimer()
                }
            }
            .onEnded { _ in
                reset()
            }
            // 同时识别长按完成事件
            .simultaneously(with: LongPressGesture()
                .onEnded { _ in
                    didTriggerLongPress = true
                    reset()
                })
    }
    
    var body: some View {
        Circle()
            .foregroundColor(.red)
            .scaleEffect(1 - longPressDuration/5)
            .gesture(gesture)
    }
    
    private func startTimer() {
        // 避免重复创建计时器
        guard longPressTimer == nil else { return }
        longPressTimer = Timer.scheduledTimer(withTimeInterval: 1/25, repeats: true) { timer in
            longPressDuration += timer.timeInterval
        }
    }
    
    private func reset() {
        longPressDuration = 0
        didTriggerLongPress = false
        longPressTimer?.invalidate()
        longPressTimer = nil
    }
}

关键修改说明

  1. DragGesture(minimumDistance: 0):捕捉所有手指按下/抬起动作,按下时启动计时器,抬起时立即重置。
  2. 组合LongPressGesture:通过simultaneously(with:)同时识别长按完成事件,触发后标记状态并重置。
  3. 拆分startTimer():增加计时器存在性判断,防止重复创建导致的异常。

内容的提问来源于stack exchange,提问作者Mischa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 09:30:51