iOS中UIDocumentPickerViewController无法记忆上次访问文件夹问题排查
解决UIDocumentPicker在iOS16+iCloud场景下无法记住上次访问文件夹的问题
问题背景
你的代码在未激活iCloud的设备上能正常记住Files应用上次访问的文件夹,但在激活iCloud的iOS16设备上每次都会打开初始目录,单独打开Files应用则无此问题。这是因为iOS14+的UIDocumentPickerViewController在iCloud场景下不会自动保留上次访问位置,需要手动指定初始目录并持久化用户的选择。
解决方案
通过以下步骤修复:
- 持久化上次访问的目录
在你的Presenter类中添加一个属性,用于保存并读取用户上次选择的文件夹URL:
private var lastAccessedDirectoryURL: URL? { get { guard let data = UserDefaults.standard.data(forKey: "LastAccessedDirectoryURL"), let url = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSURL.self, from: data) as URL? else { return nil } // 验证目录是否仍可访问 return FileManager.default.fileExists(atPath: url.path) ? url : nil } set { guard let url = newValue else { UserDefaults.standard.removeObject(forKey: "LastAccessedDirectoryURL") return } guard let data = try? NSKeyedArchiver.archivedData(withRootObject: url, requiringSecureCoding: false) else { return } UserDefaults.standard.set(data, forKey: "LastAccessedDirectoryURL") } }
- 修改文件选择器初始化代码
在创建UIDocumentPickerViewController时,为iOS14+设备设置initialDirectoryURL为上次访问的目录:
func goToFilesApp() { var documentPicker: UIDocumentPickerViewController if #available(iOS 14, *) { let types: [UTType] = [UTType.text, UTType.vCard, UTType.zip, UTType.gzip] documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: types) // 设置初始目录为上次访问的文件夹 if let lastDir = lastAccessedDirectoryURL { documentPicker.initialDirectoryURL = lastDir } } else { let types = [String(kUTTypeText), String(kUTTypeVCard), "com.pkware.zip-archive", "org.gnu.gnu-zip-archive", "org.gnu.gnu-zip-tar-archive"] documentPicker = UIDocumentPickerViewController(documentTypes: types, in: .import) } documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet // 修正原代码的视图控制器转换错误:UIView不能转为UIViewController // 确保你持有正确的UIViewController实例来present if let viewController = self as? UIViewController { viewController.present(documentPicker, animated: true, completion: nil) } // 如果是Presenter模式,替换为你持有的viewController引用,比如: // self.viewController?.present(documentPicker, animated: true) }
- 更新代理方法保存目录并获取权限
在文件选择完成后,提取文件所在目录并保存,同时获取iCloud文件的持久化访问权限:
extension AnalyzeFilePresenter : UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let firstUrl = urls.first else { return } // 获取文件所在的父目录 let directoryURL = firstUrl.deletingLastPathComponent() // 请求iCloud文件的持久化访问权限 firstUrl.startAccessingSecurityScopedResource() defer { firstUrl.stopAccessingSecurityScopedResource() } // 保存目录到本地 lastAccessedDirectoryURL = directoryURL manageFilePicked(url: firstUrl) } // 实现取消代理方法,避免内存泄漏 func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { controller.dismiss(animated: true, completion: nil) } }
关键注意事项
- 原代码中
self.view as? UIViewController是错误的,UIView无法转换为UIViewController,需确保使用正确的视图控制器实例来present选择器。 - 对于iOS14+设备,必须显式设置
initialDirectoryURL,系统在iCloud场景下不会自动保留上次访问位置。 - 保存URL到
UserDefaults时必须使用归档,因为URL不能直接存储。 - 访问iCloud文件时必须调用
startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource(),否则保存的URL后续可能无法访问。
内容的提问来源于stack exchange,提问作者Wonton
相关产品推荐
相关产品推荐

