无法 access/import 在 iOS 11 文档浏览器中选取的 iCloud 文件内容

Cannot access/import contents of iCloud file picked in iOS 11 document browser

除了打开和保存自己的自定义文档外,我的 iOS 11 基于文档浏览器的应用程序应该能够导入适当格式的文本文件并将其转换为自己的文档。

但是,当在文档浏览器中选择一个文本文件时,只有当所选文件在本地存储中时,应用程序才能访问其内容(然后执行转换),但 无法访问文件存储在 iCloud 时访问内容.

下面提供了我用于 documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) 委托方法的代码的简化注释版本:

func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) {

    guard let sourceURL = documentURLs.first else { return }

    // Check extension to verify if it is a custom document
    if sourceURL.pathExtension == "mydoc" {

        // If it is a custom document, we present it directly
        // (the presentDocument method is a standard implementation from Apple docs)
        presentDocument(at: sourceURL)

    } else {

        // Otherwise, we suppose the file is a text file to be imported
        // We first create a new document, then try to import the contents of the text file     
        let url = FileManager().temporaryDirectory.appendingPathComponent("New Document").appendingPathExtension("mydoc")
        let doc = MyDocument(fileURL: url)

        do {
            let textFileContents = try String(contentsOf: sourceURL)
            // It works fine if the text file is in local storage
            // Here the document model is updated by converting the contents of the text file 
            // (code omitted, it is actually a method in the MyDocument class)
            // ...
        } catch {
            // Produce error message: cannot access text file
            // The error message is always produced if the text file is in iCloud storage!
        }

        // Save and close the document with standard code, e.g.:               
        doc.save(to: url, for: .forCreating) { (saveSuccess) in
            guard saveSuccess else { return }
            doc.close(completionHandler: { (closeSuccess) in
                guard closeSuccess else { return }  
            })

        // Reveal and import the document
        self.revealDocument(at: url, importIfNeeded: true) { (revealedDocumentURL, error) in
           if let error = error {
               // Handle the error appropriately
               return
           }
           // Present the Document View Controller for the revealed URL
           self.presentDocument(at: revealedDocumentURL!)
        }
    }
}

info.plist 文件将自定义文档和 public.text 声明为文档类型(具有适当的角色和处理程序等级),并将自定义文档声明为导出的 UTI。如果选择的文件是自定义文档,则一切正常,无论文件是本地文件还是在 iCloud 中。

当文件在本地存储时导入工作,我认为这可能是 iCloud 权限的问题,即使 Apple 文档 UIDocumentBrowserViewController 状态:

The system automatically handles access to iCloud for you; you don't need to enable your app’s iCloud capabilities.

但是,尝试将 iCloud 功能添加到我的应用程序的权利中并没有解决问题(实际上 使情况变得更糟 ,因为现在新保存的完全相同的代码在应用程序的默认 iCloud 容器中创建文档,与文档浏览器使用的 iCloud Drive 位置不同,因此所有文档对浏览器来说都变成 "invisible" - 但是我跑题了...).

另一个 "fun" 元素是我还在应用程序中实现了 UIDocumentPickerViewController 以将文本文件中的其他内容导入到已打开的自定义文档中。我使用的代码与上面的代码基本相同......而且它工作完美,与文本文件是在本地存储还是在 iCloud 中无关!这可能会强化这样一种观点,即问题与 UIDocumentBrowserViewController.

的特定权限问题有关

因此,总而言之:我可以做些什么来访问存储在 iCloud 中的文本文件的内容并将它们转换为我的应用程序的自定义文档?预先感谢您提出任何建议(如果问题的表述有问题,请保持温和 - 这是我潜伏数月后的第一个 Whosebug 问题!)。

我不是 100% 确定,但我相信您需要在那个 URL 上调用 startAccessingSecurityScopedResource 来使用文档浏览器提供给您的沙箱扩展。不要忘记在完成后调用 stopAccessing 以免泄漏内核资源。另外,如果您不是真的在编辑该文档,您可能正在寻找 UIDocumentPickerViewController 代替?用户打开一个文件然后编辑另一个文件是很奇怪的。开始一个新文档,然后使用选择器将文本文件导入其中不是更好的 UX 吗?

插入式小片段:(Swift 5.x)

    public func documentBrowser(_ controller: UIDocumentBrowserViewController,
                                  didPickDocumentsAt documentURLs: [URL]) {

            dismiss(animated: true, completion: nil)

            self.importAll(urls: documentURLs)
        }



    final func importAll(urls: [URL]) {

        //TODO: use timer/loop/async stuff..
        // for multiple files..

        urls.forEach { (url: URL) in

            let secured = url.startAccessingSecurityScopedResource()

            = myImportInSessionFrom(url: url)

            if secured { url.stopAccessingSecurityScopedResource() }

        }
 }