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

SwiftUI实现LongTapGesture后触发Drag/MagnificationGesture及缩放假死修复

问题原因

你的实现出现缩放手势挂起的核心问题是SwiftUI对多层嵌套组合手势的状态传递存在兼容问题:你将DragGesture与MagnificationGesture的互斥组合作为LongPressGesture的序列后续手势时,缩放手势的连续状态变更和结束信号无法正确向上层传递,最终导致手势状态卡死。

修复方案

改用状态变量控制手势生效权限的实现逻辑,规避多层嵌套组合手势的bug:

  1. 新增布尔状态变量标记长按是否触发完成
  2. 单独处理长按手势,触发后放开拖动、缩放手势的使用权限
  3. 仅对拖动和缩放手势做互斥处理,避免嵌套组合手势的状态传递异常

修复后完整代码

struct DraggableCircle: View {
    enum DragState {
        case inactive
        case pressing
        case draggingOrMagnifying(translation: CGSize, magnification: CGFloat)
        
        var translation: CGSize {
            switch self {
            case .inactive, .pressing:
                return .zero
            case .draggingOrMagnifying(let translation, _):
                return translation
            }
        }
        
        var magnification: CGFloat {
            switch self {
            case .inactive, .pressing:
                return 1
            case .draggingOrMagnifying(_, let magnification):
                return magnification
            }
        }
        
        var isActive: Bool {
            switch self {
            case .inactive:
                return false
            case .pressing, .draggingOrMagnifying:
                return true
            }
        }
        
        var isDragging: Bool {
            switch self {
            case .inactive, .pressing:
                return false
            case .draggingOrMagnifying:
                return true
            }
        }
    }
    
    @GestureState var dragState = DragState.inactive
    @State var viewState = CGSize.zero
    @State var magState = CGFloat(1)
    // 新增标记:长按是否已经触发
    @State private var longPressTriggered = false
    private let minimumLongPressDuration = 0.5
    
    var body: some View {
        // 单独定义长按手势
        let longPress = LongPressGesture(minimumDuration: minimumLongPressDuration)
            .onEnded { _ in
                longPressTriggered = true
                dragState = .pressing
            }
        
        // 定义拖动手势,仅长按触发后生效
        let drag = DragGesture()
            .enabled(longPressTriggered)
            .updating($dragState) { value, state, _ in
                state = .draggingOrMagnifying(translation: value.translation, magnification: 1)
            }
            .onEnded { value in
                viewState.width += value.translation.width
                viewState.height += value.translation.height
                resetState()
            }
        
        // 定义缩放手势,仅长按触发后生效
        let magnification = MagnificationGesture()
            .enabled(longPressTriggered)
            .updating($dragState) { value, state, _ in
                state = .draggingOrMagnifying(translation: .zero, magnification: value)
            }
            .onEnded { value in
                magState *= value
                resetState()
            }
        
        // 组合手势:长按 + 拖动/缩放互斥
        let combinedGesture = longPress
            .sequenced(before: drag.exclusively(before: magnification))
            .onEnded { _ in
                resetState()
            }
        
        return Circle()
            .fill(Color.blue)
            .overlay(dragState.isDragging ? Circle().stroke(Color.white, lineWidth: 2) : nil)
            .frame(width: clipToBounds(100 * magState * dragState.magnification), height: clipToBounds(100 * magState * dragState.magnification), alignment: .center)
            .offset(
                x: viewState.width + dragState.translation.width,
                y: viewState.height + dragState.translation.height
            )
            .animation(nil)
            .shadow(radius: dragState.isActive ? 8 : 0)
            .animation(.linear(duration: minimumLongPressDuration), value: dragState.isActive)
            .gesture(combinedGesture)
    }
    
    // 统一重置状态
    private func resetState() {
        longPressTriggered = false
        dragState = .inactive
    }
    
    func clipToBounds(_ m: CGFloat) -> CGFloat {
        if abs(m) > UIScreen.main.bounds.height {
            return UIScreen.main.bounds.height
        } else {
            return abs(m)
        }
    }
}

核心修改点

  • 新增longPressTriggered状态变量控制拖动、缩放手势的生效权限,避免多层嵌套组合手势的状态传递问题
  • 拆分各个手势的独立逻辑,每个手势单独处理updating和onEnded回调,状态流转更清晰
  • 新增统一的resetState方法,所有手势结束后统一重置状态,避免状态残留导致的卡死问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 15:06:06