SwiftUI+PencilKit中绘图与手势无法同时响应问题求助
SwiftUI + PencilKit 手势冲突问题:按住按钮切换画笔工具失效
需求
在SwiftUI与PencilKit混合开发的项目中,需要实现一个功能:按住按钮时切换PKCanvasView的PKInkingTool,松开按钮后恢复原画笔工具。
现有代码实现
封装的CanvasView(UIViewRepresentable)
import PencilKit import SwiftUI struct CanvasView: UIViewRepresentable { var canvas = PKCanvasView() private var pen = PKInkingTool(.pen, color: .black , width: 2) private var eraser = PKInkingTool(.pen, color: .white , width: 2) private var marker = PKInkingTool(.marker, color: .white , width: 2) private var corrector = PKInkingTool(.marker, color: .white , width: 2) private var currentTool: PKInkingTool private var width: CGFloat = 2 init() { self.currentTool = pen } func makeUIView(context: Context) -> PKCanvasView { canvas.drawingGestureRecognizer.cancelsTouchesInView = false canvas.tool = pen return canvas } func updateUIView(_ uiView: PKCanvasView, context: Context) { DispatchQueue.main.async { uiView.becomeFirstResponder() } } mutating func setPen(color: Color) { pen.color = color.uiColor() pen.width = width currentTool = pen canvas.tool = currentTool } mutating func setCorrector(color: Color) { corrector.color = color.uiColor() corrector.width = width currentTool = corrector canvas.tool = currentTool } mutating func setMarker(color: Color) { marker.color = color.uiColor() marker.width = width currentTool = marker canvas.tool = marker } mutating func setWidth(width: CGFloat) { self.width = width currentTool.width = width canvas.tool = currentTool } mutating func setEraser(width: CGFloat) { eraser.width = width canvas.tool = eraser } func setLasso(){ canvas.tool = PKLassoTool() } func setMagicButtonTool(tool: PKInkingTool) { var newTool = tool newTool.width = width canvas.tool = newTool } func restoreTool() { canvas.tool = currentTool } }
切换按钮MagicButtonView
import SwiftUI struct MagicButtonView: View { // MARK: - View objects @Environment(\.mainWindowSize) private var mainWindowSize // MARK: - Public properties var icon: Image var color: Color var action: (_ isTapped: Bool) -> Void @State var isTapped: Bool = false // MARK: - View body var body: some View { HStack { Image.iconNotesPencil .resizable() .padding(.all, 18) } .background(Circle() .foregroundColor(color) .frame( width: mainWindowSize.proportionalWidth(with: .widthSizeRatio), height: mainWindowSize.proportionalHeight(with: .heightSizeRatio)) .overlay( Circle() .stroke( Color.colorBackgroundGray, lineWidth: 2 ) ) ) .frame( width: mainWindowSize.proportionalWidth(with: .widthSizeRatio), height: mainWindowSize.proportionalHeight(with: .heightSizeRatio)) .gesture( DragGesture(minimumDistance: 0.01) .onChanged({ _ in if !isTapped { isTapped = true action(isTapped) } }) .onEnded({ _ in isTapped = false action(isTapped) }) ) } }
父视图调用方式
MagicButtonView(icon: .iconNotesPencil, color: .red, action: { isTapped in if isTapped { canvas.setMagicButtonTool( tool: PKInkingTool( .pen, color: .red, width: 2 ) ) } else { canvas.restoreTool() } }),
问题现象
- 点击MagicButtonView时,CanvasView完全无法响应任何UIGestureRecognizer事件
- 操作CanvasView时,MagicButtonView的SwiftUI手势也无法触发
推测方向
问题源于SwiftUI与UIKit视图混合导致的手势事件抢占,或是Apple Pencil防误触机制干扰,但GoodNotes等同类应用可实现类似功能,说明存在可行的修复方案。
已尝试但无效的方案
- 给MagicButtonView的手势改用
.highPriorityGesture()/.simultaneousGesture() - 修改CanvasView的
drawingPolicy - 设置
canvas.drawingGestureRecognizer.cancelsTouchesInView=false - 调整按钮层级
- 更换手势类型
解决方案
1. 给CanvasView添加手势代理,避开按钮区域
通过协调器让PKCanvasView的手势识别器在按钮所在区域不响应触摸事件,避免抢占按钮的手势:
struct CanvasView: UIViewRepresentable { // 保留原有属性... // 添加协调器处理手势代理 class Coordinator: NSObject, UIGestureRecognizerDelegate { var parent: CanvasView var buttonFrame: CGRect = .zero // 存储按钮的全局坐标 init(parent: CanvasView) { self.parent = parent } // 判断触摸点是否在按钮区域,不在才响应画布手势 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { let touchLocation = touch.location(in: parent.canvas) let convertedButtonFrame = parent.canvas.convert(buttonFrame, from: nil) return !convertedButtonFrame.contains(touchLocation) } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } func makeUIView(context: Context) -> PKCanvasView { canvas.drawingGestureRecognizer.cancelsTouchesInView = false canvas.drawingGestureRecognizer.delegate = context.coordinator // 设置代理 canvas.tool = pen return canvas } // 新增方法:更新按钮的全局frame func updateButtonFrame(_ frame: CGRect) { if let coordinator = makeCoordinator() as? Coordinator { coordinator.buttonFrame = frame } } // 保留原有方法... }
2. 优化MagicButtonView的手势
改用LongPressGesture替代DragGesture,并设置高优先级确保手势优先触发:
struct MagicButtonView: View { // 保留原有属性... @State var isTapped: Bool = false var body: some View { HStack { Image.iconNotesPencil .resizable() .padding(.all, 18) } .background(Circle() .foregroundColor(color) .frame( width: mainWindowSize.proportionalWidth(with: .widthSizeRatio), height: mainWindowSize.proportionalHeight(with: .heightSizeRatio)) .overlay( Circle() .stroke( Color.colorBackgroundGray, lineWidth: 2 ) ) ) .frame( width: mainWindowSize.proportionalWidth(with: .widthSizeRatio), height: mainWindowSize.proportionalHeight(with: .heightSizeRatio)) // 高优先级长按手势,最小触发时长设为0,按住即响应 .highPriorityGesture( LongPressGesture(minimumDuration: 0) .onChanged { _ in if !isTapped { isTapped = true action(isTapped) } } .onEnded { _ in isTapped = false action(isTapped) } ) // 同步手势确保触摸事件可传递给底层视图 .simultaneousGesture(DragGesture(minimumDistance: 0)) } }
3. 在父视图中传递按钮的全局坐标给CanvasView
通过GeometryReader获取按钮的全局frame,传递给CanvasView让其手势识别器避开该区域:
struct ParentView: View { @State private var canvas = CanvasView() @State private var buttonFrame: CGRect = .zero var body: some View { ZStack { canvas .onAppear { DispatchQueue.main.async { canvas.canvas.becomeFirstResponder() } } MagicButtonView(icon: .iconNotesPencil, color: .red, action: { isTapped in if isTapped { canvas.setMagicButtonTool( tool: PKInkingTool( .pen, color: .red, width: 2 ) ) } else { canvas.restoreTool() } }) .background(GeometryReader { proxy in Color.clear.onAppear { buttonFrame = proxy.frame(in: .global) canvas.updateButtonFrame(buttonFrame) } }) } } }
额外优化:调整画布的绘画策略
如果存在Pencil防误触问题,可设置画布仅响应Pencil输入,避免手指触摸干扰:
func makeUIView(context: Context) -> PKCanvasView { canvas.drawingGestureRecognizer.cancelsTouchesInView = false canvas.drawingGestureRecognizer.delegate = context.coordinator canvas.drawingPolicy = .pencilOnly // 添加此行 canvas.tool = pen return canvas }
内容的提问来源于stack exchange,提问作者Alvaro Sanz Rodrigo
相关产品推荐
相关产品推荐

