我正在使用UIDocumentPickerViewController
让用户从iCloud Drive中选择文件以上传到后端。
大多数情况下,它可以正常工作。但是,有时(特别是当互联网连接不稳定时(documentPicker:didPickDocumentAtURL:
给出一个文件系统上实际上不存在的 URL,并且任何使用它的尝试都会返回 NSError "没有这样的文件或目录"。
处理这个问题的正确方法是什么?我正在考虑使用NSFileManager fileExistsAtPath:
并告诉用户如果它不存在,请重试。但这听起来不是很用户友好。有没有办法从iCloud Drive获取真正的错误原因,并告诉iCloud Drive重试?
代码的相关部分:
@IBAction func add(sender: UIBarButtonItem) {
let documentMenu = UIDocumentMenuViewController(
documentTypes: [kUTTypeImage as String],
inMode: .Import)
documentMenu.delegate = self
documentMenu.popoverPresentationController?.barButtonItem = sender
presentViewController(documentMenu, animated: true, completion: nil)
}
func documentMenu(documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
documentPicker.popoverPresentationController?.sourceView = self.view
presentViewController(documentPicker, animated: true, completion: nil)
}
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) {
print("original URL", url)
url.startAccessingSecurityScopedResource()
var error: NSError?
NSFileCoordinator().coordinateReadingItemAtURL(
url, options: .ForUploading, error: &error) { url in
print("coordinated URL", url)
}
if let error = error {
print(error)
}
url.stopAccessingSecurityScopedResource()
}
我通过在OS X上的iCloud Drive上添加两个大图像(每个~5MiB(并在iPhone上只打开其中一个(a synced file.bmp
(而不打开另一个(an unsynced file.bmp
(来重现这一点。然后关闭了无线网络。然后我尝试在我的应用程序中选择它们:
同步的文件:
original URL file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/a%20synced%20file.bmp
coordinated URL file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/CoordinatedZipFileDR7e5I/a%20synced%20file.bmp
未同步的文件:
original URL file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/an%20unsynced%20file.bmp
Error Domain=NSCocoaErrorDomain Code=260 "The file “an unsynced file.bmp” couldn’t be opened because there is no such file." UserInfo={NSURL=file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/an%20unsynced%20file.bmp, NSFilePath=/private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/an unsynced file.bmp, NSUnderlyingError=0x15fee1210 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
说明
类似的问题也发生在我身上。我像这样初始化了文档选择器:
var documentPicker: UIDocumentPickerViewController = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .import)
这意味着在documentPicker
中选择文件后,文件将复制到app_id-Inbox
目录中。当调用委托方法documentPicker(_:didPickDocumentsAt:)
时,它会给出
app_id-Inbox
URL。问题
一段时间后(没有关闭应用程序(,这些 URL 指向不存在的文件。发生这种情况是因为同时清除了tmp/
文件夹中app_id-Inbox
。例如,我选择文档,在表格视图中显示它们,然后将iPhone留在该屏幕上一分钟,然后当我尝试单击使用documentPicker
提供的URL打开QLPreviewController
文件的特定文档时,它会返回不存在的文件。
这似乎是一个错误,因为Apple的文档在此处指出
UIDocumentPickerModeImport
URL 引用所选文档的副本。这些文件 是临时文件。它们仅在您的应用程序之前保持可用 终止。要保留永久副本,请将这些文件移动到永久副本 沙盒内的位置。
它清楚地表明直到应用程序终止,但就我而言,大约是一分钟没有打开该 URL。
解决方法
将文件从文件夹移动到app_id-Inbox
tmp/
或任何其他目录,然后使用指向新位置的 URL。
斯威夫特 4
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let newUrls = urls.compactMap { (url: URL) -> URL? in
// Create file URL to temporary folder
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
// Apend filename (name+extension) to URL
tempURL.appendPathComponent(url.lastPathComponent)
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
try FileManager.default.moveItem(atPath: url.path, toPath: tempURL.path)
return tempURL
} catch {
print(error.localizedDescription)
return nil
}
}
// ... do something with URLs
}
尽管系统会处理/tmp
目录,但建议在不再需要时清除其内容。
认为问题在于tmp目录以某种方式清理。
如果您使用模拟器并从icloud打开文件,您将看到该文件存在于app-id-Inbox中,而不是清理/删除。因此,当您尝试读取文件或复制并出现文件不存在的错误时,我认为这是一个 安全问题 ,因为您可以看到文件仍然存在。
在文档选择器视图控制器的导入模式下,我用这个解决它(抱歉,我将粘贴 C# 代码而不是 swift,因为我面前有它(
在 DidPickDocument 方法中返回我所做的 NSUrl
NSData fileData = NSData.FromUrl(url);
现在你有包含文件数据的"文件数据"变量。
然后,您可以将它们复制到应用程序隔离空间中的自定义文件夹中,并且工作正常。
使用文档选取器时,用于使用文件系统将URL转换为文件路径,如下所示:
var filePath = urls[0].absoluteString
filePath = filePath.replacingOccurrences(of: "file:/", with: "")//making url to file path
我遇到了类似的问题。url 路径中的文件在一段时间后被删除。我通过使用 .open UIDocumentPickerMode 而不是 .import 来解决。
let importMenu = UIDocumentPickerViewController(documentTypes: [String(kUTTypeData)], in: .open)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
present(importMenu, animated: true, completion: nil)
在这种情况下,所选文档的 url 路径将在下面的委托方法中更改。
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print("url documentPicker:",urls)
}
您现在可以观察到路径已更改。现在我们得到了文件所在的确切路径。因此,一段时间后不会将其删除。对于iPad和模拟器,文件位于"文件提供程序存储"文件夹下,对于iPhone文件,文件将位于"文档"文件夹下。使用此路径,您还可以获取文件的扩展名和名称。
我遇到了同样的问题,但事实证明我正在删除视图中的 Temp 目录确实出现了。因此,它将在出现时保存然后删除并正确调用documentPicker:didPickDocumentAtURL:只有url会指向我删除的文件。
以下是用 Swift 5 编写的完整代码,用于支持早期版本的 iOS 14 及更高版本
此方法已从 iOS 14 中弃用
public init(documentTypes allowedUTIs: [String], in mode: UIDocumentPickerMode)
在按钮操作中编写此代码
@IBAction func importItemFromFiles(sender: UIBarButtonItem) { var documentPicker: UIDocumentPickerViewController! if #available(iOS 14, *) { // iOS 14 & later let supportedTypes: [UTType] = [UTType.image] documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes) } else { // iOS 13 or older code let supportedTypes: [String] = [kUTTypeImage as String] documentPicker = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import) } documentPicker.delegate = self documentPicker.allowsMultipleSelection = true documentPicker.modalPresentationStyle = .formSheet self.present(documentPicker, animated: true) }
实现委托
标记: - UIDocumentPickerDelegate Methods
extension MyViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
for url in urls {
// Start accessing a security-scoped resource.
guard url.startAccessingSecurityScopedResource() else {
// Handle the failure here.
return
}
do {
let data = try Data.init(contentsOf: url)
// You will have data of the selected file
}
catch {
print(error.localizedDescription)
}
// Make sure you release the security-scoped resource when you finish.
defer { url.stopAccessingSecurityScopedResource() }
}
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
我可以添加一些有趣的东西。
重复导入同一文件时,也会发生此问题。我在本地文件系统上遇到了一个非常小的文件(~900字节(的问题。
iOS 将在将文件放入收件箱并通知代理人后正好 60 秒删除该文件。如果您再次导入具有相同名称(或最后一个路径组件(的文件,这些删除操作似乎可能会排队并在 60 秒后开始造成严重破坏。这在 iOS 13(已弃用的初始值设定项(和 14(新的初始值设定项(上都会发生。
我尝试了一个经过良好测试的代码片段来执行协调删除,但它没有帮助。
这个问题非常活跃,对导入功能有很大的损害;我不认为用例非常罕见或古怪。尽管显然没有很多关于此的帖子 - 也许每个人都只是从导入切换到打开(即使如果你想成为好公民,它要求协调文件操作(?