You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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")
    }
}

三、现有代码优化建议

  1. 不要用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)")
    }
}
  1. 确保返回的previewItem是Documents目录下的合法URL,避免权限问题。

内容的提问来源于stack exchange,提问作者Shawn Frank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 06:38:11