QLPreviewController在updateContents模式下的保存时机与强制保存方法
QLPreviewController实时保存与表单输入监听解决方案
一、不关闭控制器时强制保存PDF修改
QLPreviewController没有公开的强制保存API,但可以通过以下两种方式实现实时获取修改后的文件:
方法1:触发内置保存按钮
QLPreviewController的导航栏自带保存按钮,我们可以遍历子视图找到它并模拟点击,直接触发didSaveEditedCopyOf回调,确保修改写入原文件:
// 给Coordinator添加该方法 private func triggerQLBuiltInSave(_ previewController: QLPreviewController) { guard let rightItems = previewController.navigationItem.rightBarButtonItems else { return } for item in rightItems { // 根据系统语言调整,中文环境下accessibilityLabel为"保存" if item.accessibilityLabel == "保存" || item.accessibilityLabel == "Save" { item.target?.perform(item.action) break } } }
在SwiftUI保存按钮的点击事件中,获取QLPreviewController实例并调用该方法:
// 假设持有QLPDFPreviewController的引用或通过绑定传递 if let navController = yourPreviewController.uiViewController as? UINavigationController, let previewController = navController.topViewController as? QLPreviewController { yourPreviewController.coordinator.triggerQLBuiltInSave(previewController) // 等待回调完成后执行上传逻辑,可通过CompletionHandler或Combine监听 }
方法2:利用refreshCurrentPreviewItem结合临时文件复制
调用refreshCurrentPreviewItem()会让QL将内存中的修改写入临时文件,之后将临时文件复制到原路径:
// 保存按钮点击时执行 if let navController = yourPreviewController.uiViewController as? UINavigationController, let previewController = navController.topViewController as? QLPreviewController, let currentItem = previewController.currentPreviewItem as? URL, let originalURL = yourPreviewController.url { previewController.refreshCurrentPreviewItem() // 延迟确保临时文件写入完成 DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { do { let tempURL = try currentItem.resolvingBookmarkData(options: .withoutUI, relativeTo: nil, bookmarkDataIsStale: nil) as? URL if let tempURL = tempURL { if FileManager.default.fileExists(atPath: originalURL.path) { try FileManager.default.removeItem(at: originalURL) } try FileManager.default.copyItem(at: tempURL, to: originalURL) // 此时可执行上传操作 } } catch { print("保存失败:\(error.localizedDescription)") } } }
二、监听表单文本输入的时机
QL的didUpdateContentsOf和didSaveEditedCopyOf在表单输入时不会触发,这是因为QL将表单编辑视为轻量操作,不会立刻触发文件持久化回调。可通过以下方式解决:
方法1:定时检测文件修改时间
无需依赖私有API,定时检查原PDF文件的修改时间,时间变化即说明内容更新:
// 在Coordinator中添加 private var fileMonitorTimer: Timer? private var lastModifiedDate: Date? func startFileMonitor() { guard let url = parent.url else { return } lastModifiedDate = try? FileManager.default.attributesOfItem(atPath: url.path)[.modificationDate] as? Date fileMonitorTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { [weak self] timer in guard let self = self, let url = self.parent.url else { timer.invalidate() return } let currentDate = try? FileManager.default.attributesOfItem(atPath: url.path)[.modificationDate] as? Date if currentDate != self.lastModifiedDate { self.lastModifiedDate = currentDate print("表单内容已更新") // 触发保存或上传前置逻辑 } } } // 在makeUIViewController时启动监听 func makeUIViewController(context: Context) -> UINavigationController { let controller = QLPreviewController() controller.dataSource = context.coordinator controller.delegate = context.coordinator context.coordinator.startFileMonitor() let navigationController = UINavigationController(rootViewController: controller) return navigationController }
方法2:KVO监听内部编辑状态(慎用,涉及私有API)
QLPreviewController内部的_previewContentController对象的isEditing属性会在表单输入时变化,可通过KVO监听:
// 在Coordinator的init中添加 override init(parent: QLPDFPreviewController) { self.parent = parent super.init() // 注意:私有API可能导致审核被拒 if let previewController = parent.makeUIViewController(context: Context()).viewControllers.first as? QLPreviewController, let contentController = previewController.value(forKey: "_previewContentController") { contentController.addObserver(self, forKeyPath: "isEditing", options: .new, context: nil) } } // 实现KVO回调 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "isEditing" { let isEditing = change?[.newKey] as? Bool ?? false if !isEditing { print("用户结束表单输入") // 触发保存逻辑 } } } // 移除观察者 deinit { if let previewController = parent.makeUIViewController(context: Context()).viewControllers.first as? QLPreviewController, let contentController = previewController.value(forKey: "_previewContentController") { contentController.removeObserver(self, forKeyPath: "isEditing") } }
三、现有代码优化建议
- 不要用
try?忽略错误,在didSaveEditedCopyOf中添加完整错误处理:
func previewController(_ controller: QLPreviewController, didSaveEditedCopyOf previewItem: QLPreviewItem, at modifiedContentsURL: URL) { guard let url = parent.url else { return } do { if FileManager.default.fileExists(atPath: url.path) { try FileManager.default.removeItem(at: url) } try FileManager.default.moveItem(at: modifiedContentsURL, to: url) print("文件已成功更新") } catch { print("更新文件失败:\(error.localizedDescription)") } }
- 确保返回的
previewItem是Documents目录下的合法URL,避免权限问题。
内容的提问来源于stack exchange,提问作者Shawn Frank
相关产品推荐
相关产品推荐

