如何用UIBezierPath实现PDFKit墨迹标注?标注边界不符问题
解决PDFView墨迹标注边界不符的问题
我来帮你搞定这个墨迹标注的问题!你遇到的标注边界不符的核心原因是PDF坐标系和UIKit坐标系不匹配,还有几个细节处理不到位,咱们一步步修正:
问题根源拆解
- 坐标系统差异:UIKit以左上角为原点,而PDF页面以左下角为原点,直接用触摸点的UI坐标会导致标注位置偏移、边界错误。
- Annotation边界设置错误:你直接用
pdfView.bounds作为标注的边界,这完全不符合PDF页面的坐标逻辑,应该基于转换后的路径来设置边界。 - 实时绘制逻辑错误:在
touchesMoved里直接调用path?.stroke()会干扰PDFView的原生绘制,应该维护一个临时路径用于实时预览,再在结束时转换为PDF标注。
修正后的完整代码
首先,我们需要添加一个临时路径用于实时显示绘制轨迹,然后在结束时将触摸坐标转换为PDF页面的坐标,再创建正确的PDFAnnotation:
import UIKit import PDFKit class CustomPDFView: PDFView { // 临时路径:用于实时显示绘制轨迹 private var tempPath: UIBezierPath? // 最终用于标注的路径(转换为PDF坐标后) private var pdfPath: UIBezierPath? override func draw(_ rect: CGRect) { super.draw(rect) // 绘制临时路径,实现实时预览 tempPath?.stroke() } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) guard let touch = touches.first else { return } // 初始化临时路径 tempPath = UIBezierPath() tempPath?.lineWidth = 4.0 tempPath?.lineCapStyle = .round tempPath?.lineJoinStyle = .round // 初始化PDF路径 pdfPath = UIBezierPath() pdfPath?.lineWidth = 4.0 pdfPath?.lineCapStyle = .round pdfPath?.lineJoinStyle = .round // 获取触摸点的PDF页面坐标 let pdfPoint = convertTouchToPDFPoint(touch) tempPath?.move(to: touch.location(in: self)) pdfPath?.move(to: pdfPoint) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) guard let touch = touches.first, let tempPath = tempPath, let pdfPath = pdfPath else { return } let currentTouchPoint = touch.location(in: self) let currentPDFPoint = convertTouchToPDFPoint(touch) // 更新临时路径(UI坐标) tempPath.addLine(to: currentTouchPoint) // 更新PDF路径(PDF页面坐标) pdfPath.addLine(to: currentPDFPoint) setNeedsDisplay() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) guard let touch = touches.first, let pdfPath = pdfPath else { return } // 完成最后一段路径 let finalPDFPoint = convertTouchToPDFPoint(touch) pdfPath.addLine(to: finalPDFPoint) // 创建墨迹标注 let annotationBounds = pdfPath.boundingBox let annotation = PDFAnnotation(bounds: annotationBounds, forType: .ink, withProperties: nil) annotation.add(pdfPath) // 设置标注颜色(可选,默认黑色) annotation.color = .black // 添加到当前页面 if let currentPage = currentPage { currentPage.addAnnotation(annotation) } // 清空临时路径,准备下一次绘制 tempPath = nil pdfPath = nil setNeedsDisplay() } // 核心方法:将UI触摸点转换为PDF页面的坐标 private func convertTouchToPDFPoint(_ touch: UITouch) -> CGPoint { let touchPoint = touch.location(in: self) // 将UI坐标转换为PDF页面的坐标 guard let currentPage = currentPage else { return .zero } let pageRect = currentPage.bounds(for: .mediaBox) let pdfPoint = convert(touchPoint, to: currentPage) // 调整Y轴,适配PDF坐标系(左下角为原点) return CGPoint(x: pdfPoint.x, y: pageRect.height - pdfPoint.y) } }
关键修正点说明
- 坐标转换方法:新增
convertTouchToPDFPoint方法,把UIKit的触摸点转换为PDF页面的原生坐标,解决了原点和Y轴方向的差异。 - 分离实时预览与标注路径:用
tempPath处理实时绘制的UI显示,pdfPath专门存储转换后的PDF坐标路径,避免干扰PDFView的原生渲染。 - 正确设置Annotation边界:用
pdfPath.boundingBox作为标注的边界,确保边界完全包裹绘制的墨迹内容。 - 优化路径样式:添加
lineCapStyle和lineJoinStyle为.round,让墨迹的端点和转角更平滑。
内容的提问来源于stack exchange,提问作者Mohamed Raffi
相关产品推荐
相关产品推荐

