【问题标题】:How to access files in iCloud Drive from within my iOS app?如何从我的 iOS 应用程序中访问 iCloud Drive 中的文件?
【发布时间】:2016-02-26 16:35:55
【问题描述】:

有没有办法从 iCloud Drive 中选择文件,类似于UIImagePickerController()

【问题讨论】:

    标签: ios swift icloud-drive


    【解决方案1】:

    您可以通过以下方式呈现控制器:

    import MobileCoreServices
    
    let documentPickerController = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF), String(kUTTypeImage), String(kUTTypeMovie), String(kUTTypeVideo), String(kUTTypePlainText), String(kUTTypeMP3)], inMode: .Import)
    documentPickerController.delegate = self
    presentViewController(documentPickerController, animated: true, completion: nil)
    
    

    在您的委托中实现该方法:

    func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL)
    

    请注意,您无需设置 iCloud 授权即可使用UIDocumentPickerViewController。苹果提供了示例代码来演示如何使用这个控制器here

    【讨论】:

    • 我可以自定义 UI 来替换 UIDocumentPickerViewController 吗?
    • 确保导入 MobileCoreServices
    • 是否可以从 UIDocumentPickerViewController 中选择多个文件?
    • iOS 14 中不起作用。请参阅下面的答案以获取更新。
    【解决方案2】:

    当用户选择应用沙箱之外的目的地时,文档选择器会调用委托的 documentPicker:didPickDocumentAtURL: 方法。系统将您的文档副本保存到指定的目的地。文档选择器提供副本的 URL 以指示成功;但是,您的应用无权访问此 URL 引用的文件。 Link

    此代码对我有用:

    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            let url = urls[0]
            let isSecuredURL = url.startAccessingSecurityScopedResource() == true
            let coordinator = NSFileCoordinator()
            var error: NSError? = nil
            coordinator.coordinate(readingItemAt: url, options: [], error: &error) { (url) -> Void in
                _ = 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)
    
    
                        YourFunction(tempURL)
                        return tempURL
                    } catch {
                        print(error.localizedDescription)
                        return nil
                    }
                }
            }
            if (isSecuredURL) {
                url.stopAccessingSecurityScopedResource()
            }
    
            navigationController?.dismiss(animated: true, completion: nil)
        }
    

    【讨论】:

    • let isSecuredURL = url.startAccessingSecurityScopedResource() == true - 很有帮助并解决了我的 iCloud Drive 网址问题
    • 通过这种方式,您仅在第一个文件(url)上以协调的方式执行读取相关操作。如果UIDocumentPickerViewController 中的allowsMultipleSelection 是真的呢?
    【解决方案3】:

    Swift 4.X

    您需要在 XCode Capabilities 中启用iCloud 权利。此外,您还必须在 Apple 开发者帐户的应用程序包中打开 iCloud。完成此操作后,您可以通过以下方式呈现文档选择器控制器:

    使用UIDocumentPickerDelegate 方法

    extension YourViewController : UIDocumentMenuDelegate, UIDocumentPickerDelegate,UINavigationControllerDelegate {
    
        func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
            documentPicker.delegate = self
            self.present(documentPicker, animated: true, completion: nil)
        }
    
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
            print("url = \(url)")
        }
        
        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            dismiss(animated: true, completion: nil)    
        }
    }
    

    为按钮操作添加以下代码

    @IBAction func didPressAttachment(_ sender: UIButton) {
           
            let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import)
            importMenu.delegate = self
            importMenu.modalPresentationStyle = .formSheet
            
            if let popoverPresentationController = importMenu.popoverPresentationController {
                popoverPresentationController.sourceView = sender
                // popoverPresentationController.sourceRect = sender.bounds
            }
             self.present(importMenu, animated: true, completion: nil)
    
        }
    

    【讨论】:

    • 另外UIDocumentMenuDelegate 已被弃用,您应该改用UIDocumentPickerViewController
    • 它似乎不适用于未同步的文件。我在返回的文件中有 .icloud 文件。如何确保文件已同步?
    【解决方案4】:

    Swift 5、iOS 13

    Jhonattan 和 Ashu 的回答对于核心功能来说肯定是正确的,多文档选择、错误结果和已弃用的文档选择器 API 存在许多问题。

    下面的代码展示了一个常见用例的现代从头到尾版本:选择一个外部 iCloud 文档以导入到应用程序中并对其进行处理

    请注意,您必须设置应用的 Capabilities 才能使用 iCloud 文档,并在应用的 .plist 中设置一个 ubiquity 容器...参见示例: Swift write/save/move a document file to iCloud drive

    class ViewController: UIViewController {
        
        @IBAction func askForDocument(_ sender: Any) {
            
            if FileManager.default.url(forUbiquityContainerIdentifier: nil) != nil {
    
                let iOSPickerUI = UIDocumentPickerViewController(documentTypes: ["public.text"], in: .import)
                iOSPickerUI.delegate = self
                iOSPickerUI.modalPresentationStyle = .formSheet
                
                if let popoverPresentationController = iOSPickerUI.popoverPresentationController {
                    popoverPresentationController.sourceView = sender as? UIView
                }
                self.present(iOSPickerUI, animated: true, completion: nil)
            }
        }
    
        func processImportedFileAt(fileURL: URL) {
            // ...
        }
    }
    
    extension ViewController: UIDocumentPickerDelegate, UINavigationControllerDelegate {
        
        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            dismiss(animated: true, completion: nil)
        }
        
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            if controller.allowsMultipleSelection {
                print("WARNING: controller allows multiple file selection, but coordinate-read code here assumes only one file chosen")
                // If this is intentional, you need to modify the code below to do coordinator.coordinate
                // on MULTIPLE items, not just the first one
                if urls.count > 0 { print("Ignoring all but the first chosen file") }
            }
            
            let firstFileURL = urls[0]
            let isSecuredURL = (firstFileURL.startAccessingSecurityScopedResource() == true)
            
            print("UIDocumentPickerViewController gave url = \(firstFileURL)")
    
            // Status monitoring for the coordinate block's outcome
            var blockSuccess = false
            var outputFileURL: URL? = nil
    
            // Execute (synchronously, inline) a block of code that will copy the chosen file
            // using iOS-coordinated read to cooperate on access to a file we do not own:
            let coordinator = NSFileCoordinator()
            var error: NSError? = nil
            coordinator.coordinate(readingItemAt: firstFileURL, options: [], error: &error) { (externalFileURL) -> Void in
                    
                // WARNING: use 'externalFileURL in this block, NOT 'firstFileURL' even though they are usually the same.
                // They can be different depending on coordinator .options [] specified!
            
                // Create file URL to temp copy of file we will create:
                var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
                tempURL.appendPathComponent(externalFileURL.lastPathComponent)
                print("Will attempt to copy file to tempURL = \(tempURL)")
                
                // Attempt copy
                do {
                    // If file with same name exists remove it (replace file with new one)
                    if FileManager.default.fileExists(atPath: tempURL.path) {
                        print("Deleting existing file at: \(tempURL.path) ")
                        try FileManager.default.removeItem(atPath: tempURL.path)
                    }
                    
                    // Move file from app_id-Inbox to tmp/filename
                    print("Attempting move file to: \(tempURL.path) ")
                    try FileManager.default.moveItem(atPath: externalFileURL.path, toPath: tempURL.path)
                    
                    blockSuccess = true
                    outputFileURL = tempURL
                }
                catch {
                    print("File operation error: " + error.localizedDescription)
                    blockSuccess = false
                }
                
            }
            navigationController?.dismiss(animated: true, completion: nil)
            
            if error != nil {
                print("NSFileCoordinator() generated error while preparing, and block was never executed")
                return
            }
            if !blockSuccess {
                print("Block executed but an error was encountered while performing file operations")
                return
            }
            
            print("Output URL : \(String(describing: outputFileURL))")
            
            if (isSecuredURL) {
                firstFileURL.stopAccessingSecurityScopedResource()
            }
            
            if let out = outputFileURL {
                processImportedFileAt(fileURL: out)
            }
        }
    
    }
    

    【讨论】:

    • 这个答案有效,但我做了2个小调整。在didPiskDocumentsAt urls... 方法而不是let firstURL = urls[0] 中,我使用了guard let firstURL = urls.first else { return }。在同一个方法中再往下一点,调用了`navigationController?.dismiss(...)。我不得不注释掉这一行,因为 DocumentPicker 所在的 vc 本身(vc)是以模态方式呈现的,该调用与 Document Picker 一起解雇了整个 vc。除此之外,这个答案非常有用!!!谢谢:)
    • 啊,是的,我最喜欢的两个“apple-isms”:一个编写的 API,因此您在访问必须存在的目录时必须“保护”,以及所有无数的“让这个消失”基于表示上下文的视图控制器的替代关闭机制。哈! Apple API 可以进行一些改进,您的修改是 100% 正确的。
    【解决方案5】:

    iCloudUrl.startAccessingSecurityScopedResource() // 此时对我来说返回 true,

    但是下面的代码给出了错误:

    试试 FileManager.default.createDirectory(atPath: iCloudUrl, withIntermediateDirectories: true, attributes: nil)

    “您无法保存文件“xyz”,因为该卷是只读的。”

    这确实有效:
    试试 FileManager.default.createDirectory(at: iCloudUrl, withIntermediateDirectories: true, attributes: nil)

    这是有道理的,因为该 URL 可能带有它的安全访问权限,但是这个小小的疏忽让我困扰了半天……

    【讨论】:

      【解决方案6】:

      这在 iOS 14 中再次改变了!!

      JSON 的工作示例:

      import UIKit
      import MobileCoreServices
      import UniformTypeIdentifiers
      
      func selectFiles() {
          let types = UTType.types(tag: "json", 
                                   tagClass: UTTagClass.filenameExtension, 
                                   conformingTo: nil)
          let documentPickerController = UIDocumentPickerViewController(
                  forOpeningContentTypes: types)
          documentPickerController.delegate = self
          self.present(documentPickerController, animated: true, completion: nil)
      }
      

      【讨论】:

        猜你喜欢
        • 2016-01-07
        • 1970-01-01
        • 2014-11-16
        • 2022-08-16
        • 1970-01-01
        • 2021-05-04
        • 1970-01-01
        • 1970-01-01
        • 2015-08-31
        相关资源
        最近更新 更多