UIDocumentPickerViewController设置directoryURL真机无法跳转目标文件夹
问题原因
- 模拟器权限校验逻辑宽松,未做权限处理的路径也能被
directoryURL识别跳转,真机环境下系统会严格校验路径访问权限,无有效权限的路径会被直接忽略,最终展示默认目录结构。 - 初始化时设置
asCopy: true会触发系统的文件导入模式,该模式下真机不会响应沙盒外路径的定位请求,直接跳转到默认文件选择根目录。 - 初始化时将
forOpeningContentTypes设为[.folder]属于参数配置错误,系统会按照传入的类型过滤所有非匹配项,自然无法展示PDF文件。 - 直接存储跨会话使用的外部目录裸URL会在真机上失效,系统不会为无安全作用域的URL保留访问权限,必须通过bookmark持久化权限。
修复步骤
持久化目标文件夹的访问权限
首次选中目标文件夹时,不要直接存储URL本身,需要生成可持久化的安全bookmark数据存储,每次使用前从bookmark解析出带有效访问权限的URL,核心代码如下:// 选中目标文件夹时调用,保存权限 func saveFolderPermission(for folderURL: URL) { guard folderURL.startAccessingSecurityScopedResource() else { return } defer { folderURL.stopAccessingSecurityScopedResource() } guard let bookmarkData = try? folderURL.bookmarkData( options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil ) else { return } UserDefaults.standard.set(bookmarkData, forKey: "TargetFolderBookmark") } // 读取目标文件夹URL时调用,返回带有效权限的URL func getValidTargetFolderURL() -> URL? { guard let bookmarkData = UserDefaults.standard.data(forKey: "TargetFolderBookmark") else { return nil } var isStale = false let url = try? URL( resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale ) guard !isStale else { return nil } return url }如果目标文件夹是当前App沙盒Documents目录下的子目录,不需要做bookmark处理,直接传入沙盒路径即可。
调整UIDocumentPicker初始化配置
去掉初始化参数中的asCopy: true,保持支持类型为[.pdf],在设置directoryURL前先激活路径的安全访问权限,修改后的makeUIViewController代码如下:func makeUIViewController(context: Context) -> UIDocumentPickerViewController { let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf]) picker.delegate = context.coordinator if let validFolderURL = getValidTargetFolderURL() { validFolderURL.startAccessingSecurityScopedResource() picker.directoryURL = validFolderURL } return picker }手动实现文件复制逻辑(替代原asCopy效果)
原asCopy: true的作用是用户选中文件后自动将文件复制到App临时目录,去掉该参数后,可以在选中回调中手动实现复制逻辑,效果完全一致:func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { // 释放目录访问权限 if let folderURL = controller.directoryURL { folderURL.stopAccessingSecurityScopedResource() } guard let selectedPDFURL = urls.first, selectedPDFURL.startAccessingSecurityScopedResource() else { return } defer { selectedPDFURL.stopAccessingSecurityScopedResource() } // 复制PDF到App沙盒目录 let targetPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent(selectedPDFURL.lastPathComponent) try? FileManager.default.copyItem(at: selectedPDFURL, to: targetPath) documentURL = targetPath }
注意:如果需要适配iOS 16以下版本,
directoryURL属性在iOS 15及更早的真机系统上存在偶发不生效的系统bug,上述权限处理逻辑可以覆盖绝大多数场景,极少数系统级bug无代码层面的通用解决方案。
内容的提问来源于stack exchange,提问作者Denis
相关产品推荐
相关产品推荐

