大多数浏览器和
Developer App 均支持流媒体播放。
-
PDFKit 的新功能
了解 PDFKit,这个功能全面的框架可帮助您的 App 查看、编辑和保存 PDF 文档。我们将介绍 PDFKit 的最新功能,包括支持实时文本和表格、从图片创建 PDF、构建交互式叠层,以及保存注释等。
资源
相关视频
WWDC22
WWDC19
-
下载
Conrad: 我是 Conrad 今天 我将为大家介绍 PDFKit 更新的内容 以下是我们的议程 我们首先快速回顾下 PDFKit 然后 了解更新的内容 包括实时文本和表单 将 PDF 页面转为图片的新方式 最后 overlay views (覆盖视图) 首先 我们先快速回顾下 PDFKit 是如何运行的 PDFKit 是一个全功能框架 能帮助您的 App 查看 编辑和编写 PDF 文件 在 iOS macOS 和 Mac Catalyst 中均适用 也可以通过可将 UI 视图 整合到您 APP 中的 封装 UIViewRepresentable 在 SwiftUI 中使用 PDFKit PDFKit 包含四个核心类 覆盖您 App 中 所需的大部分功能 PDFView 是您使用 SwiftUI 或 Interface Builder 加入到布局中的小组件 可展示 PDF 文档的内容 允许用户导航 设置缩放比例 及将文本复制到剪贴板
PDFDocument 代表 一个 PDF 文件 这对 PDFDocument 子类来说 并不常见 但您总是会用到 它是 PDF 对象图的根源 是树之躯干 无枝不成树
每个文档都包含一个或多个 PDFPage 对象 页面渲染内容和存储资源 如字体和图像 对页面来说都是独特的
我们对象图的绿叶 是 PDFAnnotations 这些是可选的 然而 PDFPage的内容 并不是用于编辑 annotations 则具有交互特性 通常是可编辑的 这些对象在我们今天的内容中 都扮演了对应的角色 您可通过下方链接中 关于“Introducing PDFKit”的精彩内容 来了解更多 PDFKit 的基本原理
现在 我们来看下在 iOS 16 和 macOS Ventura 中推出的新功能
PDFKit 当前已支持实时文本 在照片 App 中 文本通常 占少数 您可以直接点击复制 而在 PDF 中 如果您看到的是文本 则普遍都是文本 人们也希望其发挥文本的功能 别无他用 而现在 使用实时文本 您可以在 PDF 文档中 选择和搜索文本 如这一示例 这只是一个扫描的位图 完全没有文本
当然 PDFs 有很多页面 您打开文档时 不会想对一个 PDF 文档的每个页面 都费力进行 OCR 因此 在您与每个页面发生 交互动作时 PDFKit 应需启用 OCR 即时完成 无需生成 文档副本 如果您选择保存整份文档的文本 保存时也可以选择对应的选项
除实况文本以外 PDFKit的 表单处理也进行了改进 即使没有内置文本区域 也能自动识别 包含表单区域的文档 您可以点击这些文本区域 输入文本 正如您所想的那样
接下来 我们说下从图片 创建 PDF 页面的新 API
在 iOS 16 和 macOS Ventura 中 有一个全新的 灵活的 API 可以让您的 App 用图片 作为输入 创建 PDF 页面 您的 App 用 CGImageRef 提供图片 PDFKit 使用您提供的 CGImageRef 并将其用高质量的 JPEG 解码进行压缩 由于 CGImageRef 是 CoreGraphics 中的原生数据类型 所以无需额外转换
有多个选项可供您处理 最常见的情况
MediaBox 指定页面大小 您可以选择适合图片的大小 或选择纸张尺寸 如 Letter
Rotation 可供您指定纵向或横向
还有 UpscaleIfSmaller 如果图片大于 MediaBox 则默认将图片缩小到适应比例 如果 UpscaleIfSmaller 已被指定 该规则仍有效 但如果图片较小 则会增加尺寸以适合页面
现在 我们来回答下 大家都在问的问题 “我怎么能用 PencilKit 在 PDF 页面上绘图呢” 答案就是使用覆盖视图
过去 在 PDFs 上绘图的 唯一方法 就是将 PDFPage 编入子类 覆写绘图方法 或使用 自定义 PDF annotations 但从 iOS 16 和 macOS Ventura 开始 现在可以 在每个 PDF 页面上 覆盖您自己的视图 让您的 App 创建实时 具有强交互性的视图 显示在 PDF 页面上层 关于覆盖视图 你需要知道 以下三点 首先 您要使用一个新代理 在 PDF 页面中 安装您的覆盖视图
到了需要保存的时候 您需要将内容并入 PDF 说到保存 我们会分享一些 保存 PDF 文档的 最优方法
在 PDF 页面上安装覆盖视图 很简单 因为 PDFs 可以包含 成百上千的页面 打开 PDF 时 您不会想为这些 所有页面创建视图 但如果用户快速前后滚动呢 您如何知道何时创建视图
幸运的是 PDFKit 已经设计好 在用户滚动至所需位置前 就能智能准备内容 它知道何时请求覆盖视图 您的 App 只需要响应它 通过新代理发出的请求
PDFPageOverlayViewProvider 是新代理 对了 PDFKitPlatformView 只是 UIView 或 NSView 的定义 取决于平台 你需要运行的最重要方法 是 overlayViewForPage 只需要提供您的视图实例 PDFKit 就会应用适当的边界 重设其尺寸 如果页面有非零旋转 它会执行旋转
接下来两个方法是可选的 willDisplayOverlayView 可用于 安装您自己的动作处理程序 或与 PDFKit 的动作处理程序 建立失败关系
在 PDFKit 完成您的视图时 会调用 willEndDisplayingOverlayView 可能是因为页面已滚动到 视图之外 您可在这里发布您的视图 但这一方法还有另一个重要用途 假如您的视图有一些 展示其正在绘制内容的数据 您可以用该方法获取数据 并将其放置一边 我们会用 PencilKit 进行示例 但如果您的视图数据 被搁置在别处 则无需执行此操作
在示例中 这是我们用于 提供程序的类 它执行 PDFPageOverlayViewProvider 代理 这是 iOS 所以 PDFKitPlatformView 是一个 UIView 使用地图从 PDFPage 到 UIView 这是占位符代理方法 接下来 我们看下实现 overlayViewForPage 检查 其 pageToView 地图 看是否对指定页面建立了视图 如果没有 则创建一个新视图 不管哪种情况 我们都能从页面 获取绘图 并将其放置于画布视图 在这些示例中 我使用的是 PDFPage 的子类 其作用是添加“绘制”属性
现在 我们来看下一个方法 即 WillEndDisplayingOverlay
willEndDisplayingOverlayView 很简单 它从视图中获取绘图 并将其存储于我们自定义页面类 现在我们已经完成了 那就来看下实际的实施吧
通常 每年大约这个时候 我会在缅因州钓鱼 然而现在 我却在 WWDC 现场 因此 有另一个人代替我去旅行 我将会给他看下 一些我最喜欢的地点 并且会用这一 App 来完成 在覆盖视图中 使用 PencilKit 这一 App 包含我们刚刚 看到的代码 仅此而已 获取屏幕上的覆盖视图的 完整代码仅约 30 行 因此 Grand Lake Stream 这是大坝池 有很多鱼 大部分动作 都是在这里进行的 您沿着这条小路一直走 经过树林 就能到达大坝池 然后钓鱼 您可以钓各种鱼 或者也可以走这条路 越过大坝 从这里往下走 您可以从那里钓鱼 一直到这里 环岛一圈 下来这里 但不管你做什么 不要越过这里 这里的水流又深又急 避开这个地方 下来这里 到孵化场 沿着孵化场旁一直走 进入这个池塘 您可以在这里抛钓丝 这是很好的地方 我经常在这里钓鱼
好了 现在我们在这个页面 做了一些标记 我们演示下缩放和滚动
看它响应速度多快
这就好了 PDFKit 的覆盖视图 现在 您已经有了这些草图 那要怎么保存呢 我们可以用 PDFAnnotation 类 来完成 保存时 我们希望能实现两点 我们希望在高保真的前提下 匹配屏幕显示 并且我们希望做一些往返编辑 PDF annotations 有一些功能 可以实现这些要求 PDF annotations 有一个 “appearance stream” 是 PDF 绘制指令流 基本上所有您能用 Quartz2D 绘制的东西 appearance stream 都能记录下来 只要能渲染成图片的都能记录 如果我们使用 Metal 用的就是这个方法 而且 由于是作为 PDF 绘图 来记录的 看起来和在 Adobe Reader Chrome 等的显示都一样
在 PDF 文档中 PDF annotations 是以字典的形式来存储的 这意味着我们也可以在 私人密钥/值对中存储自定义数据 那我们来看看代码是什么样的 首先创建 PDFAnnotation 的子类 这一步是为了覆写 draw() 方法 PDFKit 在保存我上一张幻灯片中 讲的appearance stream 时 会调用此方法
要保存文档 我们要 覆写 UIDocument 的 contents() 这是该函数的概览 对应稍后的上下文 我们依次通过 PDFDocument 的各页面 下一步再来充实循环
每个页面我们都进行以下操作 创建我们自定义类的 annotation 将我们的绘图编码至数据 将数据添加至 annotation 我们下次打开该文档时 就可以使用 value:forAnnotationKey 来回读存储的绘图数据 并将其放置到我们的覆盖视图中
最后 将 annotation 添加到页面 回到我们的 contents() 覆写 现在我们页面上 已经添加了 annotations 我们用 PDFDocument 的 dataRepresentation() 返回结果
当您的内容被存为 annotation 时 文档接收者可对其进行移动 重设大小或删除 通常来说 那正是您想要的 但有时 您会希望您的 annotations 作为页面的一部分烧录 在 iOS 16 和 macOS Ventura 中 有一个新的 PDFDocumentWriteOption 可以简化该操作 只要添加 burnInAnnotationsOption = true 到保存选项 即可完成
说到 PDF 编写选项 有一些 在 iOS 16 和 macOS Ventura 中 已可用 我们来看看 CoreGraphics 一直致力于 让 PDFs 中保存的图片 维持最高保真度 因此图片都以无损压缩 全解析度保存 如果 PDF 在大幅面打印机中 打印出来 那堪称完美 但是更有可能的是 图片是在屏幕上显示的 这些高保真度的图片数据 带来的结果就是文件尺寸超大 为了应对这一问题 我向大家介绍 以下两个选项
saveAllImagesAsJPEG 可完成 刚刚所说的操作 不管图片是如何创建的 都可以通过 JPEG 保存 解码入 PDF
optimizeImagesForScreen 可对 图片进行下采样 直至 HiDPI 屏幕分辨率的最大值 这两个选项可以同时使用
createLinearizedPDF 创建 一种特殊类型的 PDF 针对网络进行优化 PDF 格式 最初在互联网出现前 就已设计为 从文件末尾读取 这意味着它要提前下载 所需的只是展示 一个线性化 PDF 有了展示文件 开始的第一页 所需的所有内容 因此网页浏览器可先显示该内容 并同时加载剩余部分
您可以将这些选项传送到 PDFDocument 的 dataRepresentation 或 writeToURL 方法 正是如此 PDFKit 强大 简便 今天在 iOS 和 macOS 以及有新功能的 iOS 16 和 macOS Ventura 中 许多 App 都在使用 我已经迫不及待要看看 您的成果了
请查看下方的讲座 了解更多信息 感谢大家的观看
-
-
6:54 - Implementing the overlay protocol
class Coordinator: NSObject, PDFPageOverlayViewProvider { var pageToViewMapping = [PDFPage: UIView]() func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? { var resultView: PKCanvasView? = nil if let overlayView = pageToViewMapping[page] { resultView = overlayView } else { let canvasView = PKCanvasView(frame: .zero) canvasView.drawingPolicy = .anyInput canvasView.tool = PKInkingTool(.pen, color: .systemYellow, width: 20) canvasView.backgroundColor = UIColor.clear pageToViewMapping[page] = canvasView resultView = canvasView } // If we have stored a drawing on the page, set it on the canvas let page = page as! MyPDFPage if let drawing = page.drawing { resultView?.drawing = drawing; } return resultView } func pdfView(_ pdfView: PDFView, willEndDisplayingOverlayView overlayView: UIView, for page: PDFPage) { let overlayView = overlayView as! PKCanvasView let page = page as! MyPDFPage page.drawing = overlayView.drawing pageToViewMapping.removeValue(forKey: page) } }
-
10:22 - Create a subclass of PDFAnnotation
// Implement a subclass with a drawing override class MyPDFAnnotation: PDFAnnotation { override func draw(with box: PDFDisplayBox, in context: CGContext) { UIGraphicsPushContext(context) context.saveGState() let page = self.page as! MyPDFPage if let drawing = page.drawing { let image = drawing.image(from: drawing.bounds, scale: 1) image.draw(in: drawing.bounds) } context.restoreGState() UIGraphicsPopContext() } }
-
10:35 - Add annotations to your document when saving
override func contents(forType typeName: String) throws -> Any { if let pdfDocument = pdfDocument { // Go through all pages in the document for i in 0...pdfDocument.pageCount-1 { if let page = pdfDocument.page(at: i) { if let drawing = (page as! MyPDFPage).drawing { // Create an annotation of our custom subclass let newAnnotation = MyPDFAnnotation(bounds: drawing.bounds, forType: .stamp, withProperties: nil) // Add our custom data let codedData = try! NSKeyedArchiver.archivedData(withRootObject: drawing, requiringSecureCoding: true) newAnnotation.setValue(codedData, forAnnotationKey: PDFAnnotationKey(rawValue: "drawingData")) // Add our annotation to the page page.addAnnotation(newAnnotation) } } } // -- Option #1: Save the document to a data representation if let resultData = pdfDocument.dataRepresentation() { return resultData } // -- Option #2: Save the document to a data representation and "burn in" annotations let options = [PDFDocumentWriteOption.burnInAnnotationsOption: true] if let resultData = pdfDocument.dataRepresentation(options: options) { return resultData } } // Fall through to returning empty data return Data() }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。