24

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)并a synced file.bmp在 iPhone 上只打开其中一个()而不打开另一个(an unsynced file.bmp)来复制这一点。然后关闭WiFi。然后我尝试在我的应用程序中选择它们:

同步文件:

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

7 回答 7

37

描述

我也遇到过类似的问题。我已经像这样初始化了文档选择器:

var documentPicker: UIDocumentPickerViewController = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .import)

这意味着文件app_id-Inbox在被选中后会被复制到目录中documentPicker。当委托方法documentPicker(_:didPickDocumentsAt:)被调用时,它会给出指向app_id-Inbox目录中文件的 URL。

问题

一段时间后(没有关闭应用程序),这些 URL 指向不存在的文件。发生这种情况是因为同时清除app_id-Inbox了文件夹中的内容。tmp/例如,我选择文档,在表格视图中显示它们并将 iPhone 留在该屏幕上大约一分钟,然后当我尝试单击QLPreviewController使用从它提供的 URL中打开文件的特定文档时,documentPicker它返回的文件不存在。

这似乎是一个错误,因为 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目录,但建议在不再需要时清除其内容。

于 2017-12-28T12:43:21.060 回答
6

我认为问题不在于 tmp 目录已通过某种方式进行清理。

如果您使用模拟器并从 icloud 打开一个文件,您会看到该文件存在于 app-id-Inbox 中并且没有被清理/删除。因此,当您尝试读取文件或复制并收到文件不存在的错误时,我认为这是一个安全问题,因为您可以看到该文件仍然存在。

在 DocumentPickerViewController 的导入模式下,我用这个来解决它(对不起,我将粘贴 c# 代码而不是 swift,因为我有它在我面前)

在返回我所做的 NSUrl 的 DidPickDocument 方法中

NSData fileData = NSData.FromUrl(url);

现在你有了包含文件数据的“fileData”变量。

然后您可以将它们复制到应用程序隔离空间中您的自定义文件夹中,它工作得很好。

于 2018-04-17T13:39:00.680 回答
5

在使用文档选择器时,为了使用文件系统,请将 url 转换为文件路径,如下所示:

var filePath = urls[0].absoluteString
filePath = filePath.replacingOccurrences(of: "file:/", with: "")//making url to file path
于 2019-01-30T20:46:15.143 回答
1

我遇到了同样的问题,但事实证明我正在删除视图中的 Temp 目录确实出现了。所以它会在出现并正确调用 documentPicker:didPickDocumentAtURL: 时保存然后删除:只有 url 会指向我已删除的文件。

于 2017-03-04T00:41:33.923 回答
1

我面临着类似的问题。一段时间后,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 和模拟器,文件位于“File Provider Storage”文件夹下,iPhone 文件位于“Document”文件夹下。使用此路径,您还可以获得文件的扩展名和名称。

于 2021-06-28T12:48:10.647 回答
0

我可以为此添加一些有趣的东西。

重复导入同一个文件时也会出现此问题。我在本地文件系统上遇到了一个非常小的文件(~900 字节)的问题。

iOS 将在将文件放入收件箱并通知委托人后恰好 60 秒删除文件。如果您再次导入具有相同名称(或最后一个路径组件)的文件,这些删除操作似乎会排队并在 60 秒后开始造成严重破坏。这发生在 iOS 13(不推荐使用的初始化程序)和 14(新的初始化程序)上。

我尝试了一个经过良好测试的代码片段来执行协调删除,但它没有帮助。

这个问题非常活跃,对导入功能造成了巨大的损害;我不认为用例是极其罕见或古怪的。尽管显然没有很多关于此的帖子-也许每个人都只是从导入切换到打开(即使如果您想成为好公民,它要求协调文件操作)?

于 2021-12-15T18:35:21.360 回答
0

这是用Swift 5编写的完整代码,用于支持早期版本的iOS 14 及更高版本

此方法已从 iOS 14 弃用

public init(documentTypes allowedUTIs: [String], in mode: UIDocumentPickerMode)
  1. 在您的按钮操作中编写此代码

    @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)
         }
    
  2. 实施代表

// MARK: - UIDocumentPickerDelegate 方法

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)
  }
}
于 2021-09-04T11:09:57.347 回答