SwiftUI:触摸缩放圆形,轻触后持续缩放的问题排查与修复
问题描述
需求:实现一个圆形,用户按住时缓慢缩小,在两种情况停止并重置缩放至1:
- 检测到LongPressGesture触发(长按完成)
- 手势结束(手指抬起)
现有代码长按功能正常,但轻触时圆形会持续缩小且不停止。控制台输出如下:
- 轻触仅打印一次
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 } }
关键修改说明
DragGesture(minimumDistance: 0):捕捉所有手指按下/抬起动作,按下时启动计时器,抬起时立即重置。- 组合
LongPressGesture:通过simultaneously(with:)同时识别长按完成事件,触发后标记状态并重置。 - 拆分
startTimer():增加计时器存在性判断,防止重复创建导致的异常。
内容的提问来源于stack exchange,提问作者Mischa
相关产品推荐
相关产品推荐

