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

如何用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)
    }
}

关键修正点说明

  1. 坐标转换方法:新增convertTouchToPDFPoint方法,把UIKit的触摸点转换为PDF页面的原生坐标,解决了原点和Y轴方向的差异。
  2. 分离实时预览与标注路径:用tempPath处理实时绘制的UI显示,pdfPath专门存储转换后的PDF坐标路径,避免干扰PDFView的原生渲染。
  3. 正确设置Annotation边界:用pdfPath.boundingBox作为标注的边界,确保边界完全包裹绘制的墨迹内容。
  4. 优化路径样式:添加lineCapStyle和lineJoinStyle为.round,让墨迹的端点和转角更平滑。

内容的提问来源于stack exchange,提问作者Mohamed Raffi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:04:00