基于OverlayView将PencilKit批注烧录进PDF的技术问询
解决方案:将PencilKit批注烧录进PDF+S3外部PDF优化方案
一、核心需求实现:把PencilKit批注合并到PDF文件中
你当前的代码是将批注单独保存为.dat文件,现在需要修改为直接把PKDrawing渲染到PDF页面中,让批注和PDF合为一体。
关键代码修改
在OverlayCoordinator类中替换原来的saveDrawings方法,新增合并批注到PDF的逻辑:
class OverlayCoordinator: NSObject, PDFPageOverlayViewProvider, PDFViewDelegate { var pageToViewMapping = [PDFPage: PKCanvasView]() var toolPicker = PKToolPicker() var targetS3URL: URL? // 用于后续上传回S3的目标地址 // 将批注合并到PDF并保存 func mergeDrawingsIntoPDF(pdfView: PDFView) { guard let document = pdfView.document else { return } // 先清除页面上已有的墨水批注(避免重复添加) for page in document.pages { let inkAnnotations = page.annotations.filter { $0.type == .ink } inkAnnotations.forEach { page.removeAnnotation($0) } } // 将每个页面的PKDrawing转换为PDF批注 for (page, canvasView) in pageToViewMapping { let pageBounds = page.bounds(for: .mediaBox) let scale = pdfView.scaleFactorForSizeToFit // 创建PDF墨水批注 let inkAnnotation = PDFAnnotation(bounds: pageBounds, forType: .ink, withProperties: nil) inkAnnotation.color = .black // 可根据Pencil工具动态调整颜色 // 把PKStroke转换为PDF路径 let paths = canvasView.drawing.strokes.map { stroke -> PDFBezierPath in let path = PDFBezierPath() guard let firstPoint = stroke.points.first else { return path } path.move(to: firstPoint.location.applying(CGAffineTransform(scaleX: scale, y: scale))) for point in stroke.points.dropFirst() { path.addLine(to: point.location.applying(CGAffineTransform(scaleX: scale, y: scale))) } path.lineWidth = CGFloat(stroke.toolWidth) * scale return path } inkAnnotation.addPaths(paths) page.addAnnotation(inkAnnotation) } // 保存到临时目录(避免占用永久存储) guard let pdfData = document.dataRepresentation() else { return } let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("annotated_\(UUID().uuidString).pdf") do { try pdfData.write(to: tempURL) print("合并后的PDF已保存到临时目录") // 上传回S3(后续实现) if let s3URL = targetS3URL { uploadToS3(localURL: tempURL, targetURL: s3URL) } // 上传成功后删除临时文件 try FileManager.default.removeItem(at: tempURL) } catch { print("PDF处理失败: \(error)") } } // S3上传逻辑(可结合AWS SDK优化) private func uploadToS3(localURL: URL, targetURL: URL) { var request = URLRequest(url: targetURL) request.httpMethod = "PUT" // 根据S3配置添加必要的请求头(如签名、Content-Type等) let task = URLSession.shared.uploadTask(with: request, fromFile: localURL) { _, response, error in if let error = error { print("S3上传失败: \(error)") return } guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { print("上传返回异常状态码") return } print("批注PDF已成功上传回S3") } task.resume() } // 其余原有代码... }
二、外部S3 PDF优化方案:减少本地存储占用
1. 用URLSession流式下载PDF到临时目录
替代原来的Data(contentsOf:)同步下载,改用URLSession异步流式下载到系统临时目录,避免占用永久存储:
struct PDFDocumentView: View { let s3PDFURL = URL(string: "你的S3文件地址")! @State private var pdfTempURL: URL? @State private var saveAnnotations = false var body: some View { VStack { Button(action: { saveAnnotations.toggle() }) { Text("保存批注") } if let tempURL = pdfTempURL { PDFViewWrapper(pdfTempURL: tempURL, targetS3URL: s3PDFURL, saveAnnotations: $saveAnnotations) .frame(maxWidth: .infinity, maxHeight: .infinity) .edgesIgnoringSafeArea(.all) } else { ProgressView("加载PDF中...") .onAppear { downloadPDFToTemp(from: s3PDFURL) { tempURL in pdfTempURL = tempURL } } } } } // URLSession流式下载到临时目录 private func downloadPDFToTemp(from url: URL, completion: @escaping (URL?) -> Void) { let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("temp_\(UUID().uuidString).pdf") let task = URLSession.shared.downloadTask(with: url) { location, _, error in guard let location = location, error == nil else { completion(nil) return } do { try FileManager.default.moveItem(at: location, to: tempURL) completion(tempURL) } catch { print("临时文件移动失败: \(error)") completion(nil) } } task.resume() } }
2. 优化PDFViewWrapper传递参数
修改PDFViewWrapper以接收临时PDF地址和S3目标地址,并在保存触发时调用合并逻辑:
struct PDFViewWrapper: UIViewControllerRepresentable { let pdfTempURL: URL let targetS3URL: URL @Binding var saveAnnotations: Bool let coordinator = OverlayCoordinator() func makeUIViewController(context: Context) -> UIViewController { coordinator.targetS3URL = targetS3URL let pdfView = PDFView() pdfView.pageOverlayViewProvider = coordinator pdfView.delegate = coordinator pdfView.isInMarkupMode = true pdfView.displayMode = .singlePageContinuous pdfView.displayDirection = .vertical if let document = PDFDocument(url: pdfTempURL) { pdfView.document = document } let viewController = UIViewController() viewController.view.addSubview(pdfView) pdfView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ pdfView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor), pdfView.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor), pdfView.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor), pdfView.topAnchor.constraint(equalTo: viewController.view.topAnchor) ]) // 视图消失时自动保存批注 viewController.viewDidDisappear = { _ in if let pdfView = viewController.view.subviews.first as? PDFView { coordinator.mergeDrawingsIntoPDF(pdfView: pdfView) } } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { if saveAnnotations { if let pdfView = uiViewController.view.subviews.first as? PDFView { coordinator.mergeDrawingsIntoPDF(pdfView: pdfView) } saveAnnotations = false } } }
三、核心优势
- 批注永久合并:直接将批注写入PDF文件,下次打开无需加载额外批注文件
- 减少本地存储:所有PDF操作都在临时目录完成,上传后自动清理,不占用用户永久存储空间
- 高效流式处理:用URLSession异步下载/上传,避免同步操作阻塞UI,同时降低内存占用
内容的提问来源于stack exchange,提问作者Joseph Garcia
相关产品推荐
相关产品推荐

