SwiftUI实现类似Procreate双指平移画布且不冲突子手势的方案咨询
解决方案
提供两种可行方案,你可以根据项目支持的系统版本选择:
方案1:优化现有UIKit实现(兼容低版本iOS)
核心修改点
- 实现手势识别代理,允许手势同时识别
- 关闭手势默认的触摸取消逻辑,避免拦截子视图手势
- 调整视图层级,避免覆盖子视图交互区域
修改后的TwoFingerNavigationView代码
// 新增遵循UIGestureRecognizerDelegate struct TwoFingerNavigationView: UIViewRepresentable { var draggedCallback: ((CGPoint) -> Void) var dragEndedCallback: (() -> Void) class Coordinator: NSObject, UIGestureRecognizerDelegate { var draggedCallback: ((CGPoint) -> Void) var dragEndedCallback: (() -> Void) init(draggedCallback: @escaping ((CGPoint) -> Void), dragEndedCallback: @escaping (() -> Void)) { self.draggedCallback = draggedCallback self.dragEndedCallback = dragEndedCallback } @objc func dragged(gesture: UIPanGestureRecognizer) { if gesture.state == .ended { self.dragEndedCallback() } else { self.draggedCallback(gesture.translation(in: gesture.view)) } } // 允许和其他手势同时识别 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } // 只有检测到双指的时候才触发当前手势,避免抢单指手势 func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { guard let pan = gestureRecognizer as? UIPanGestureRecognizer else { return false } return pan.numberOfTouches == 2 } } func makeUIView(context: UIViewRepresentableContext<TwoFingerNavigationView>) -> UIView { let view = UIView(frame: .zero) let gesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.dragged)) gesture.minimumNumberOfTouches = 2 gesture.maximumNumberOfTouches = 2 // 关键:不取消传递到子视图的触摸事件 gesture.cancelsTouchesInView = false gesture.delaysTouchesBegan = false gesture.delegate = context.coordinator view.addGestureRecognizer(gesture) return view } func makeCoordinator() -> Coordinator { return Coordinator(draggedCallback: self.draggedCallback, dragEndedCallback: self.dragEndedCallback) } func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<TwoFingerNavigationView>) { } }
调整使用方式
不要将导航视图放在ZStack的顶层,改为挂载到ZStack的背景上,避免覆盖子视图的交互:
ZStack { OtherView() .gesture(TapGesture().onEnded { // 此处手势可正常触发 }) } .background { TwoFingerNavigationView(draggedCallback: { translation in var newLocation = startLocation ?? location newLocation.x += (translation.x / scale) newLocation.y += (translation.y / scale) self.location = newLocation }, dragEndedCallback: { startLocation = location }) }
方案2:SwiftUI原生实现(支持iOS 16+)
iOS 16及以上版本可以直接使用原生手势的touchCount修饰符实现双指拖动,搭配simultaneousGesture避免和子视图手势冲突:
struct CanvasView: View { @State private var location: CGPoint = .zero @State private var startLocation: CGPoint? @State private var scale: CGFloat = 1.0 var twoFingerDrag: some Gesture { DragGesture(minimumDistance: 0) .touchCount(2) // 限定双指触发 .onChanged { value in var newLocation = startLocation ?? location newLocation.x += (value.translation.width / scale) newLocation.y += (value.translation.height / scale) self.location = newLocation } .onEnded { _ in startLocation = location } } var body: some View { ZStack { OtherView() .gesture(TapGesture().onEnded { // 子视图手势正常工作 }) } // 用simultaneousGesture让当前手势和子视图手势同时识别,不会互相冲突 .simultaneousGesture(twoFingerDrag) } }
内容的提问来源于stack exchange,提问作者bob_mosh
相关产品推荐
相关产品推荐

