I am using a DocumentPickerViewController to import a document in my app. The picker does it's work, there are no bugs and no crashes but every time after a document is picked or the view controller is canceled I get a log with the following error:
[DocumentManager] The view service did terminate with error:
Error Domain=_UIViewServiceErrorDomain Code=1 "(null)"
UserInfo={Terminated=disconnect method}
This is the function where the picker VC will be presented:
#IBAction func presentDocumentPicker(_ sender: Any) {
let documentPicker = UIDocumentPickerViewController(documentTypes: ["com.adobe.pdf"], in: .import)
documentPicker.delegate = self
present(documentPicker, animated: false, completion: nil)
}
This is the delegate extension:
extension ViewController: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
print(url)
}
public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
print("Canceled")
}
}
I also tried using the UIDocumentBrowserViewController but I get the same log. Another thing I tried is enabling the iCloud entitlements but there is no change. With a nav controller and just pushing the DocumentPicker renders the same result. Every test I did was built on both a real device and a simulator.
Is there anything I'm doing wrong? Is there anyway I can get rid of that log showing up?
I have a problem with iCloud file import
I'm getting empty options after I choose the place to import from (iCloud, google drive, dropbox ... etc)
I have data on iCloud drive and the entitlements are right
So what is wrong with this code or is there anything about the settings may cause this problem?
here is the code
#IBAction func selectionButtonAction(_ sender: Any) {
let types = [kUTTypePDF as String ,kUTTypePNG as String, kUTTypeImage as String,kUTTypeJPEG as String]
UINavigationBar.appearance().isTranslucent = true
let documentMenu = UIDocumentMenuViewController(documentTypes: types, in: .import)
documentMenu.delegate = self
self.viewController?.present(documentMenu, animated: true, completion: nil)
}
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentMenu.delegate = self
self.viewController?.present(documentMenu, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
if controller.documentPickerMode == .import {
// do staff
}
}
Here is what I get When I run the app on iOS 10
When I Press choose File
then after pressing on iCloud
That's what I get if I run the app on iOS 9:
I'm sure I have data there like here in the iCloud Drive application
And the entitlement
I had the same problem, when trying to present a already used Instance of the UIDocumentMenuViewController.
I was instantiating it lazily like this:
lazy var documentMenuController: UIDocumentMenuViewController = {
let controller = UIDocumentMenuViewController(documentTypes: ["com.adobe.pdf"], in: .import)
controller.delegate = self
return controller
}()
Which produced this error every time except the first time.
So I changed the code to this:
var documentMenuController: UIDocumentMenuViewController {
let controller = UIDocumentMenuViewController(documentTypes: ["com.adobe.pdf"], in: .import)
controller.delegate = self
return controller
}
To obtain a fresh instance on every access of the property.
I have a document picker, but after selecting a document, the didPickDocumentAt part is never triggered. It was working before I updated swift, is there something that is different now?
func selectDocument(_ sender: UIButton!){
let documentPickerVC = UIDocumentPickerViewController(documentTypes: ["org.openxmlformats.wordprocessingml.document", "com.microsoft.word.doc"], in: UIDocumentPickerMode.import)
documentPickerVC.delegate = self
self.present(documentPickerVC, animated: true, completion: nil)
}
func documentPicker(_ documentPicker: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
print("file picked: \(url)")
documentPicker.dismiss(animated: true, completion: nil)
}
Nothing is 'failing' either, it just isn't calling that documentPicker method.
I have a similar one for selecting media and that one works fine...
func selectMedia(_ sender: UIButton!){
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.photoLibrary){
picker.delegate = self
picker.allowsEditing = false
picker.mediaTypes = [kUTTypeMovie as String]
self.present(picker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:[String: Any]) {
let url = info[UIImagePickerControllerMediaURL] as! URL
print("media picked: \(url)")
}
Edit:
I just added the documentPickerWasCancelled method, and for some reason that is being called when I select a document.
Note I am selecting a document from google drive, would that have an affect on anything?
Edit 2:
Answered, uninstalled and reinstalled and it worked. See answer below. Thanks everyone for the suggestions.
The above code looks like it is correct. I uninstalled the google drive app (where I was getting files from) and reinstalled it and then it worked as expected. I also tried from dropbox and that worked as well.
Not sure what was making it fail before.
I want to get documents from Cloud services like iCloud , google drive and dropbox on a button click (like in WhatsApp screenshot below), does anyone know how to do it in swift ? Thanks in advance
From your project's capabilities. First enable both the iCloud services and the Key-Sharing, import MobileCoreServices in your class and finally extended the following three classes inside your UIViewController :
UIDocumentMenuDelegate,UIDocumentPickerDelegate,UINavigationControllerDelegate
Implement the following functions :
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
let myURL = url as URL
print("import result : /(myURL)")
}
public func documentMenu(_ documentMenu:UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
present(documentPicker, animated: true, completion: nil)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
print("view was cancelled")
dismiss(animated: true, completion: nil)
}
How to call all of this? Add the following bit of code to your click function..
func clickFunction(){
let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
self.present(importMenu, animated: true, completion: nil)
}
Click your button. The following menu will pop up ..
In the case of DropBox. Upon clicking on any item. You will be redirected back to your app and the URL will be logged in your terminal.
Manipulate the documentTypes to your need. In my app, Users permitted to Pdf only. So, suit yourself.
kUTTypePDF
Also if you feel like customizing your own menu bar. Add the following code and customize your own function inside the handler
importMenu.addOption(withTitle: "Create New Document", image: nil, order: .first, handler: { print("New Doc Requested") })
Enjoy it.
First enable iCloud documents Capabilitie, see Apple documentation here
The you have to use UIDocumentMenuViewController
let importMenu = UIDocumentMenuViewController(documentTypes: doctypes, inMode: .Import)
importMenu.delegate = self
importMenu.popoverPresentationController?.barButtonItem = self.addButon;
self.presentViewController(importMenu, animated: true, completion: nil)
Is there a way to choose file from iCloud Drive similar way to UIImagePickerController()?
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
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)
}
}
}
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)
}
This changed once again in iOS 14!!
Working example for 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)
}
Swift 4.X
You need to enable iCloud entitlements in XCode Capabilities. Also you have to turn on iCloud in you app bundle in developer account of Apple. Once you do this, you are able to present document picker controller by following way:
Use UIDocumentPickerDelegate methods
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)
}
}
Add below code for Button Action
#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)
}
iCloudUrl.startAccessingSecurityScopedResource()
// is returning true for me at this point,
However the following code gave the error:
try FileManager.default.createDirectory(atPath: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
"You can’t save the file “xyz” because the volume is read only."
This actually works :
try FileManager.default.createDirectory(at: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
Which makes sense because the URL probably is carrying around it’s security access, but this little oversight stumped me for half a day…
For my swiftUI users: It's quite easy.
struct HomeView: View {
#State private var showActionSheet = false
var body: some View {
Button("Press") {
showActionSheet = true
}
.fileImporter(isPresented: $showActionSheet, allowedContentTypes: [.data]) { (res) in
print("!!!\(res)")
}
}
}
I use
try FileManager.default.copyItem(at: url, to: destinationUrl)
instead of moveItem. Otherwise, the files are removed from iCloud Drive, which is not what I want.