在iOS 11 PDFKit文档上实现墨迹注释

我想允许用户在PDFView中查看iOS 11 PDFKit文档。 图纸最终应嵌入PDF中。

后者我通过向PDFPage添加类型为“ink”的PDFAnnotation来解决,其中UIBezierPath对应于用户的绘图。

但是,如何实际记录用户在PDFView上创建的触摸以创建这样的UIBezierPath?

我已尝试在PDFView和PDFPage上覆盖touchesBegan,但它永远不会被调用。 我试过添加一个UIGestureRecognizer,但没有做任何事情。

我假设我需要事后使用PDFView实例方法转换(_ point:CGPoint,到页面:PDFPage)将获得的坐标转换为适合注释的PDF坐标。

最后,我通过创建一个扩展UIViewController和UIGestureRecognizerDelegate的PDFViewController类来解决问题。 我添加了一个PDFView作为子视图,并将一个UIBarButtonItem添加到navigationItem,用于切换注释模式。

我在名为signingPath的UIBezierPath中记录触摸,并使用以下代码在currentAnnotation中使用类型PDFAnnotation的当前注释:

override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { let position = touch.location(in: pdfView) signingPath = UIBezierPath() signingPath.move(to: pdfView.convert(position, to: pdfView.page(for: position, nearest: true)!)) annotationAdded = false UIGraphicsBeginImageContext(CGSize(width: 800, height: 600)) lastPoint = pdfView.convert(position, to: pdfView.page(for: position, nearest: true)!) } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { let position = touch.location(in: pdfView) let convertedPoint = pdfView.convert(position, to: pdfView.page(for: position, nearest: true)!) let page = pdfView.page(for: position, nearest: true)! signingPath.addLine(to: convertedPoint) let rect = signingPath.bounds if( annotationAdded ) { pdfView.document?.page(at: 0)?.removeAnnotation(currentAnnotation) currentAnnotation = PDFAnnotation(bounds: rect, forType: .ink, withProperties: nil) var signingPathCentered = UIBezierPath() signingPathCentered.cgPath = signingPath.cgPath signingPathCentered.moveCenter(to: rect.center) currentAnnotation.add(signingPathCentered) pdfView.document?.page(at: 0)?.addAnnotation(currentAnnotation) } else { lastPoint = pdfView.convert(position, to: pdfView.page(for: position, nearest: true)!) annotationAdded = true currentAnnotation = PDFAnnotation(bounds: rect, forType: .ink, withProperties: nil) currentAnnotation.add(signingPath) pdfView.document?.page(at: 0)?.addAnnotation(currentAnnotation) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { let position = touch.location(in: pdfView) signingPath.addLine(to: pdfView.convert(position, to: pdfView.page(for: position, nearest: true)!)) pdfView.document?.page(at: 0)?.removeAnnotation(currentAnnotation) let rect = signingPath.bounds let annotation = PDFAnnotation(bounds: rect, forType: .ink, withProperties: nil) annotation.color = UIColor(hex: 0x284283) signingPath.moveCenter(to: rect.center) annotation.add(signingPath) pdfView.document?.page(at: 0)?.addAnnotation(annotation) } } 

注释切换按钮只运行:

 pdfView.isUserInteractionEnabled = !pdfView.isUserInteractionEnabled 

这真的是它的关键,因为这会禁用PDF上的滚动并使我能够接收触摸事件。

记录触摸事件并立即转换为PDFAnnotation的方式意味着在PDF上书写时注释是可见的,并且最终将其记录到PDF中的正确位置 – 无论滚动位置如何。

确保它最终出现在右页只是类似地将页面编号的硬编码0更改为pdfView.page(for:position,nearest:true)值。

我已经通过创建一个新的视图类(例如Annotate View)并在用户注释时置于PDFView之上来完成此操作。

此视图使用默认的touchesBegan / touchesMoved / touchesEnded方法在手势后创建贝塞尔曲线路径。 触摸结束后,我的视图会将其作为注释保存在pdf上。

注意:您需要一种方法让用户确定它们是否处于注释状态。

对于我的主要课程

 class MyViewController : UIViewController, PDFViewDelegate, VCDelegate { var pdfView: PDFView? var touchView: AnnotateView? override func loadView() { touchView = AnnotateView(frame: CGRect(x: 0, y: 0, width: 375, height: 600)) touchView?.backgroundColor = .clear touchView?.delegate = self view.addSubview(touchView!) } func addAnnotation(_ annotation: PDFAnnotation) { print("Anotation added") pdfView?.document?.page(at: 0)?.addAnnotation(annotation) } } 

我的注释视图

 class AnnotateView: UIView { var path: UIBezierPath? var delegate: VCDelegate? override func touchesBegan(_ touches: Set, with event: UIEvent?) { // Initialize a new path for the user gesture path = UIBezierPath() path?.lineWidth = 4.0 var touch: UITouch = touches.first! path?.move(to: touch.location(in: self)) } override func touchesMoved(_ touches: Set, with event: UIEvent?) { // Add new points to the path let touch: UITouch = touches.first! path?.addLine(to: touch.location(in: self)) self.setNeedsDisplay() } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touch = touches.first path?.addLine(to: touch!.location(in: self)) self.setNeedsDisplay() let annotation = PDFAnnotation(bounds: self.bounds, forType: .ink, withProperties: nil) annotation.add(self.path!) delegate?.addAnnotation(annotation) } override func touchesCancelled(_ touches: Set, with event: UIEvent?) { self.touchesEnded(touches, with: event) } override func draw(_ rect: CGRect) { // Draw the path path?.stroke() } override init(frame: CGRect) { super.init(frame: frame) self.isMultipleTouchEnabled = false } } 

添加到jksoegaard的优秀答案,对像我这样的新手有一些澄清:

  1. 您需要在项目中包含UIBezierPath + .swift才能识别.moveCenter和rect.center。 从https://github.com/xhamr/paintcode-path-scale下载。
  2. 可以排除这些行:

     UIGraphicsBeginImageContext(CGSize(width: 800, height: 600)) 

  let page = pdfView.page(for: position, nearest: true)! 
  1. 您需要在函数之外声明一些全局变量:

     var signingPath = UIBezierPath() var annotationAdded : Bool? var lastPoint : CGPoint? var currentAnnotation : PDFAnnotation? 
  2. 最后,如果你想要墨水更宽更好,你需要做两件事:

一个。 每次在看到annotation.add或currentAnnotation.add之前,您需要(根据该函数的需要使用注释或currentAnnotation):

  let b = PDFBorder() b.lineWidth = { choose a pixel number here } currentAnnotation?.border = b currentAnnotation?.color=UIColor.{ your color of choosing } 

我建议为颜色指定低alpha值。 结果是美丽的,并受到你的中风速度的影响。 例如,红色将是:

  UIColor(red: 255/255.0, green: 0/255.0, blue: 0/255.0, alpha: 0.1) 

湾 记录每次触摸的矩形需要适应较粗的线条。 代替

  let rect = signingPath.bounds 

尝试,以10px的厚度为例:

  let rect = CGRect(x:signingPath.bounds.minX-5, y:signingPath.bounds.minY-5, width:signingPath.bounds.maxX- signingPath.bounds.minX+10, height:signingPath.bounds.maxY- signingPath.bounds.minY+10) 

重要说明 :touchesEnded函数也使用currentAnnotation变量。 你必须在该函数中重复rect的定义(短的或上面建议的那个),并在那里重复currentAnnotation的定义:

  currentAnnotation = PDFAnnotation(bounds: rect, forType: .ink, withProperties: nil) 

如果不这样做,单击一下就不会移动会使您的应用程序崩溃。

我可以validation保存文件后,会保留注释。 保存示例代码:

  let pdfData = pdfDocument?.dataRepresentation() let annotatedPdfUrl = URL(fileURLWithPath: "\ (NSSearchPathForDirectoriesInDomains(.documentsDirectory, .userDomainMask, true)[0])/AnnotatedPDF.pdf") try! pdfData!.write(to: annotatedPdfUrl)