SwiftUI使用DocumentPicker拷贝文件到应用报错Generation not found
SwiftUI DocumentPicker 文件导入"Generation not found"报错修复
问题场景
在iPhone端基于SwiftUI开发时,通过DocumentPicker唤起系统文件应用,引导用户选择图片/文件导入到应用沙盒,初始化UIDocumentPickerViewController时已经设置asCopy: true,但在代理回调读取选中文件URL内容时出现如下报错:
Error Domain=GSLibraryErrorDomain Code=3 "Generation not found" UserInfo={NSDescription=Generation not found}
操作演示:
报错核心原因
- 即使设置了
asCopy: true,系统回调返回的URL是带安全作用域的临时URL,回调触发时跨进程文件拷贝可能还未完成,且未主动申请安全域访问权限就直接读取,会触发资源不存在的错误 - 原代码直接用
String(contentsOf:encoding:)读取所有选中文件,但配置支持的PDF、图片、二进制文件都不是UTF8文本编码,强行读取文本本身就会失败 - 原代码没有主动将文件拷贝到应用自身沙盒的持久化目录,系统返回的临时URL会在回调结束后被系统回收权限,异步访问或者后续重启App都会失效
- 原代码配置了
.folder类型,但没有实现文件夹遍历逻辑,直接读取文件夹URL必然失败
正确实现代码
import SwiftUI import UniformTypeIdentifiers // 调用端示例 struct DemoView: View { @State private var showDocumentPicker = false @State private var pickedFileData: Data? @State private var pickedTextContent: String? var body: some View { Button("选择导入文件") { showDocumentPicker = true } .sheet(isPresented: $showDocumentPicker, onDismiss: { showDocumentPicker = false }) { DocumentPicker( pickedData: $pickedFileData, pickedText: $pickedTextContent, supportedTypes: [.text, .pdf, .jpeg, .png, .gif, .data] ) } } } struct DocumentPicker: UIViewControllerRepresentable { @Binding var pickedData: Data? @Binding var pickedText: String? let supportedTypes: [UTType] func makeCoordinator() -> Coordinator { Coordinator(pickedData: $pickedData, pickedText: $pickedText) } func makeUIViewController(context: Context) -> UIDocumentPickerViewController { let controller = UIDocumentPickerViewController( forOpeningContentTypes: supportedTypes, asCopy: true ) controller.delegate = context.coordinator controller.allowsMultipleSelection = false return controller } func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} } class Coordinator: NSObject, UIDocumentPickerDelegate { @Binding var pickedData: Data? @Binding var pickedText: String? init(pickedData: Binding<Data?>, pickedText: Binding<String?>) { _pickedData = pickedData _pickedText = pickedText } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let sourceURL = urls.first else { return } // 申请安全域访问权限 let canAccess = sourceURL.startAccessingSecurityScopedResource() // 无论流程成功失败,最后自动释放权限 defer { if canAccess { sourceURL.stopAccessingSecurityScopedResource() } } do { // 主动拷贝文件到自身沙盒Documents目录,持久化存储 let sandboxDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let targetURL = sandboxDir.appendingPathComponent(sourceURL.lastPathComponent) // 重名文件先删除避免拷贝失败,也可以加唯一后缀区分版本 if FileManager.default.fileExists(atPath: targetURL.path) { try FileManager.default.removeItem(at: targetURL) } try FileManager.default.copyItem(at: sourceURL, to: targetURL) // 从沙盒路径读取文件内容,无权限问题 let fileData = try Data(contentsOf: targetURL) pickedData = fileData // 仅当文件是UTF8文本时才解析字符串 if let text = String(data: fileData, encoding: .utf8) { pickedText = text } } catch { print("文件导入失败: \(error)") } } }
关键注意事项
- 如果开启多选支持,需要遍历
urls数组中所有URL,每个文件单独走安全域权限申请、拷贝流程 - 如果需要支持选择文件夹,对返回的文件夹URL用
FileManager.default.enumerator(at:)遍历内部子文件后逐个拷贝,不能直接读取文件夹URL - 必须在
didPickDocumentsAt回调的当前执行流内完成权限申请和拷贝操作,不要异步dispatch到其他队列再访问系统返回的原始URL,否则系统会提前回收临时权限触发报错 - 必须用
defer保证stopAccessingSecurityScopedResource被调用,否则会造成内核资源泄漏,导致后续文件访问异常 - 图片、PDF、二进制文件读取到Data后,再对应转成
UIImage、PDFDocument等业务需要的类型,不要强行转UTF8字符串
内容的提问来源于stack exchange,提问作者Ufuk Köşker
相关产品推荐
相关产品推荐

