PDFKit 是苹果在 iOS11 后推出的框架,它提供一系列 API 方便我们展示和操作 PDF 文档。

PDFKit 常用于展示和操作 PDF 文档,我们可以从这两方面展开探索:

  • View:视图,展示能力
  • Model:模型,操作能力

一、视图

1. PDFView

PDFKit 提供了一个叫 PDFView 的 UIView 子类,它可以用于展示 PDF 文档内容,而且基本包含了所有基本的 PDF 浏览器的功能:查看、缩放、选择文本等等。当然它还提供多种属性用于自定义页面的布局等。

  1. // 创建 PDFView
  2. let pdfView = PDFView(frame: view.bounds)
  3. view.addSubview(pdfView)
  4. // 展示形式:单页,双页等等
  5. pdfView.displayMode = .singlePageContinuous
  6. // 接收 PDFDocument 类型属性,来展示已存在的 PDF 文档。
  7. pdfView.document = PDFDocument(url: pdfDocumentURL)

2. PDFThumbnailView

PDFThumbnailView 顾名思义,它直接提供了一个展示 PDF 内容缩略图的视图。
image.png

  1. let thumbnail = PDFThumbnailView(frame: frame)
  2. view.addSubview(thumbnail)
  3. // 为 thumbnail 配置 PDFView
  4. thumbnail.pdfView = pdfView
  5. thumbnail.thumbnailSize = CGSize(width: 50, height: 50)
  6. thumbnail.layoutMode = .horizontal

二、模型

在 PDFKit 中, PDFDocument 就等同于实际的 PDF 文件,它包含 PDF 文件的元信息等。而 PDFPage 则是管理每一页的渲染,添加标注 PDFAnnotation 等。下面这幅图展示了这三个基本模型的关系。
image.png

1. PDFDocument

读写操作

  1. // 从文件 URL 中读取
  2. if let documentURL = Bundle.main.url(forResource: "test", withExtension: "pdf"),
  3. let document = PDFDocument(url: documentURL) {
  4. pdfView.document = document
  5. }
  6. // 保存文档
  7. document.write(to: documentURL)
  8. // 保存并加密文档,这里还可以配置上 owner 和 user 两种权限
  9. document.write(to: documentURL,
  10. withOptions: [.ownerPasswordOption: "kiwi",
  11. .userPasswordOption: "kiwi123"])

页面操作

  1. // 从文件中获取 PDFPage
  2. let myPage = document.page(at: 0)
  3. // 添加 Page
  4. document.insert(newPage, at: document.pageCount)
  5. // 交换 Page 位置
  6. document.exchangePage(at: 0, withPageAt: 1)
  7. // 删除 Page
  8. document.removePage(at: document.pageCount - 1)

解密与权限

PDFDocument 可以判断文档是否被加密,并提供解密方法。同时还可以获取到当前文档用户的权限等。

  1. let document = PDFDocument(url: documentURL)
  2. // 判断是否加密文档,并通过密码解锁
  3. if document.isEncrypted && document.unlock(withPassword: "kiwi") {
  4. // 判断文档用户权限
  5. if document.permissionsStatus == .owner {
  6. // Owner 权限
  7. } else {
  8. // User 权限
  9. if document.allowsCopying { /* ... */ }
  10. if document.allowsPrinting { /* ... */ }
  11. }
  12. }

2. PDFPage

获取文本

我们可以直接通过 PDFPage 的 string 属性获取到当前 Page 的文本信息,也可以通过 selection(for: range) 方法获取部分文本信息。还可以通过 attributedString 属性获取到富文本信息。

  1. let page = pdfDocument.page(at: 0)
  2. page?.string // 文本
  3. page?.attributedString // 富文本
  4. // 通过 NSRange 指定选择的文本内容
  5. let pageSelection = page?.selection(for: NSRange(location: 10, length: 5))
  6. pageSelection?.string
  7. pageSelection?.attributedString

自定义 PDFPage

我们可以通过继承 PDFPage,并重写其 draw(with box: PDFDisplayBox, to context: CGContext) 方法来绘制一些自定义的内容,比如给 PDF 文件添加上 水印

  1. // 1. 配置 PDFDocumentDelegate
  2. pdfDocument.delegate = self
  3. // 2. 实现 PDFDocumentDelegate 的 classForPage 方法
  4. func classForPage() -> AnyClass {
  5. // 这里返回自定义的 PDFPage:WatermarkPage
  6. return WatermarkPage.self
  7. }
  8. // 3.
  9. class WatermarkPage: PDFPage {
  10. override func draw(with box: PDFDisplayBox, to context: CGContext) {
  11. // 绘制原始内容
  12. super.draw(with: box, to: context)
  13. //绘制水印
  14. /*...*/
  15. }
  16. /*...*/
  17. }

WatermarkPage 的具体绘制实现可以查看苹果提供的官方 Demo-CustomGraphics

3. PDFAnnotation

我们可以在 PDFPage 上添加标注 PDFAnnotation ,创建标注时可以指定内建的标注类型PDFAnnotationSubtype,比如线段,矩形,下划线等。比如下面的代码表示创建一条红色带箭头的线段:

  1. // 创建 PDFAnnotationSubtype 为 line 的标注
  2. let line = PDFAnnotation(bounds: view.bounds, forType: .line, withProperties: nil)
  3. line.startPoint = CGPoint(x: 0, y: 0)
  4. line.endPoint = CGPoint(x: 100, y: 100)
  5. line.startLineStyle = .closedArrow
  6. line.endLineStyle = .openArrow
  7. line.color = UIColor.red
  8. let border = PDFBorder()
  9. border.lineWidth = 5
  10. line.border = border
  11. pdfPage.addAnnotation(line)

PDFAction

PDFAnnotaion 有一个 PDFAction 类型的属性,PDFAction 有这几种具体类型:

  • PDFActionGoTo
  • PDFActionNamed
  • PDFActionRemoteGoTo
  • PDFActionResetForm
  • PDFActionURL

PDFActionGoTo 可以跳转到文档指定的位置:

  1. let destination = PDFDestination(page: pdfPage, at: CGPoint(x: 30, y: 200))
  2. let actionGoTo = PDFActionGoTo(destination: destination)
  3. annotation.action = actionGoTo

PDFActionURL 可以打开一个 URL 地址:

  1. let url = URL(string: "http://meshkit.cn")
  2. let actionURL = PDFActionURL(url: url)
  3. annotation.action = actionURL

其余的 PDFAction 这里就不展开描述了,更多的可以查看官方文档: PDFAction

Widgets

PDFAnnotationSubtype 有一种类型是 widget ,它代表一类特殊的 Annotations,比如是按钮、单选按钮,复选框等这些可交互的控件。
image.png
创建 PDFAnnotaion 指定 PDFAnnotationSubtype 为 widget 后,还需要指定 PDFAnnotationWidgetSubtypePDFWidgetControlType 才能确定某一类型的 widget:

  1. // 复选框☑️
  2. let checkbox = PDFAnnotation(bounds: bounds, forType: .widget, withProperties: nil)
  3. checkbox.widgetFieldType = .button
  4. checkbox.widgetControlType = .checkBoxControl
  5. page.addAnnotation(checkbox)
  6. // 按钮
  7. let button = PDFAnnotation(bounds: bouds, forType: .widget, withProperties: nil)
  8. button.widgetFieldType = .button
  9. button.widgetControlType = .pushButtonControl
  10. button.caption = "按钮"
  11. page.addAnnotation(button)
  12. // 文本输入框
  13. let textField = PDFAnnotation(bounds: bounds, forType: .widget, withProperties: nil)
  14. textField.widgetFieldType = .text
  15. page.addAnnotation(textField)
  16. /*....*/

三、小结

总的来说,PDFKit 给我们提供了非常易用且方便扩展的处理 PDF 的能力。PDFKit 更多的是用于展示和在原有的 PDF 文档上进行标注等,至于从零创建并简单排版 PDF 文档,它提供的能力有限,关于这块的探索,我会在下一个篇文章讲解。

参考