如何在WKWebView上层叠加PKCanvasView 兼容Pencil绘图与网页滚动
优化集成方案
原hacky方案的核心问题
你之前搭的可运行版本灵活度差,根源是破坏了两个系统组件的原生事件链路:
- 把PKCanvasView的绘图手势直接摘下来挂到WKWebView上,相当于切断了PKCanvasView和PencilKit服务的原生响应者绑定,导致toolPicker状态同步、绘制精度管理、自定义手势优先级的能力全部受限
- 强制让PKCanvasView的hitTest返回nil,没有建立可配置的触控分流逻辑,所有Pencil事件的处理完全依赖硬编码的手势类型过滤,想加自定义规则都找不到合适的切入点
具体实现步骤
基于系统触控事件的分发逻辑,完全不需要手动调用touches系列方法做转发,只要在WKWebView的手势代理层做精准的类型分流,就能同时满足两个核心需求:保持WKWebView对所有手指触控的原生处理能力,不改变滚动、点击、缩放的原生表现;完整保留PKCanvasView和PencilKit的全量能力,toolPicker、压感识别、撤销重做等特性都不受影响。
- 自定义WKWebView子类,实现触控类型分流逻辑,不需要重写webView内部的事件处理流程
class FilteredWebView: WKWebView, UIGestureRecognizerDelegate { // 绑定上层canvas的绘图手势,用于接收Pencil类型的事件 weak var targetDrawingGesture: UIGestureRecognizer? override init(frame: CGRect, configuration: WKWebViewConfiguration) { super.init(frame: frame, configuration: configuration) // 给webView的所有内置手势设置代理,在事件分发前做类型判断 gestureRecognizers?.forEach { $0.delegate = self } } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { // 判断为Apple Pencil触控时:不交给webView处理,直接转发给canvas的绘图手势 if touch.type == .pencil { targetDrawingGesture?.receiveTouch(touch) return false } // 手指触控完全放行,走webView原生处理逻辑 return true } } - 调整视图层级的交互配置,不再重写PKCanvasView的hitTest方法:
- 保持webView在下层、透明PKCanvasView在上层的层级关系
- 把PKCanvasView的
isUserInteractionEnabled设置为false,让所有触控事件默认穿透到下层的webView,不需要靠返回nil的hitTest做穿透 - 保留PKCanvasView对自身
drawingGestureRecognizer的持有关系,把这个手势赋值给webView的targetDrawingGesture属性 - 删掉之前把canvas的
drawingGestureRecognizer手动添加到webView上的代码,避免手势重复接收事件
- 保持原有PencilKit的绑定逻辑不变:
- 继续用
toolPicker.addObserver(canvas)绑定toolPicker和canvas,不需要调整第一响应者的设置 - 不需要额外修改canvas绘图手势的allowedTouchTypes属性,所有Pencil事件的接收都由webView的分流逻辑精准控制
- 继续用
方案的实际优势
- 无侵入打断WKWebView的原生触控链路:手指触发的滚动、点击、表单交互、缩放手势完全走系统原生流程,不存在滚动偏移、手势卡顿的问题
- 完整保留PencilKit全量能力:绘图手势始终归PKCanvasView所有,toolPicker的状态同步、压感/倾斜度识别、橡皮擦/画笔的效果、图层透明度配置都和独立使用PKCanvasView时完全一致
- 灵活度可自由扩展:可以随时在分流逻辑里加自定义规则,比如临时禁用绘制、加入双指触控的撤回手势、或者根据网页滚动状态调整绘制的可用范围,不需要改动核心的事件分发逻辑
补充:很多人尝试在hitTest或者touchesBegan阶段判断触控来源,经常会遇到拿不到准确touch类型的问题,其实UIGestureRecognizerDelegate的回调时机刚好是在系统完成触控事件路由前,这里拿到的touch.type是完全准确的,完全不需要手动转发touches方法。
内容的提问来源于stack exchange,提问作者Amir Omidi
相关产品推荐
相关产品推荐

