Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access files in iCloud Drive from within my iOS app?

Is there a way to choose file from iCloud Drive similar way to UIImagePickerController()?

like image 565
Bartłomiej Semańczyk Avatar asked Nov 24 '15 09:11

Bartłomiej Semańczyk


People also ask

Can I access iCloud from an app?

Use the Files App to View iCloud Drive Files on an iPhone or iPad. The Files app is an intuitive file browser that you can pair with a range of cloud storage services, like iCloud, Google Drive, or Dropbox. You can even use Files to look at the documents saved locally on your device.

How do I see files in iCloud Drive?

Click on iCloud Drive and then type in your search criteria in the search box. It should bring up any documents with the same name in the iCloud Drive folder.


3 Answers

You can present controller the following way:

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)

In your delegate implement the method:

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

Note that you don't need to set up iCloud Entitlement to use UIDocumentPickerViewController. Apple provides sample code that demonstrates how to use this controller here

like image 158
Bartłomiej Semańczyk Avatar answered Oct 21 '22 13:10

Bartłomiej Semańczyk


Swift 5, iOS 13

Jhonattan's and Ashu's answers are definitely on the right track for the core functionality, there are a number of issues with multiple-document-selection, error outcomes and deprecated document picker API.

The code below shows a modern start-to-finish version of a common use case: pick an external iCloud document to import into app and do something with it.

Note that you have to have your app's Capabilities set up to use iCloud documents and have a ubiquity container set up in your app's .plist... See for example: 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)
        }
    }

}
like image 21
Bill Patterson Avatar answered Oct 21 '22 13:10

Bill Patterson


The document picker calls the delegate’s documentPicker:didPickDocumentAtURL: method when the user selects a destination outside your app’s sandbox. The system saves a copy of your document to the specified destination. The document picker provides the copy’s URL to indicate success; however, your app does not have access to the file referred to by this URL. Link

This code work for me:

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)
    }
like image 4
Jhonattan Avatar answered Oct 21 '22 11:10

Jhonattan