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.
Related
I'm newbie in iOS development, so some things which I will show and ask here can be stupid and please don't be angry :) So, I need to add support of picking files from local storage in my app. This feature will be used for picking file -> encoding to Base64 and then sending to remote server. Right now I have some problems with adding this functionality to my app. I had found this tutorial and did everything what was mentioned here:
added import - import MobileCoreServices
added implementation - UIDocumentPickerDelegate
added this code scope for showing picker:
let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeText),String(kUTTypeContent),String(kUTTypeItem),String(kUTTypeData)], in: .import)
documentPicker.delegate = self
self.present(documentPicker, animated: true)
and also added handler of selected file:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(urls)
}
In general file chooser appears on simulator screen, but I see warning in XCode:
'init(documentTypes:in:)' was deprecated in iOS 14.0
I visited the official guideline and here also found similar info about deprecation some method. So, how I can solve my problem with file choosing by the way which will be fully compatible with the latest iOS version. And another question - how I can then encode selected file? Right now I have an ability of file choosing and printing its location, but I need to get its data like name, content for encoding and some others. Maybe someone faced with similar problems and knows a solution? I need to add it in ordinary viewcontroller, so when I tried to add this implementation:
UIDocumentPickerViewController
I saw such error message:
Multiple inheritance from classes 'UIViewController' and 'UIDocumentPickerViewController'
I will be so pleased for any info: tutorials or advice :)
I decided to post my own solution of my problem. As I am new in ios development my answer can contain some logical problems :) Firstly I added some dialogue for choosing file type after pressing Attach button:
#IBAction func attachFile(_ sender: UIBarButtonItem) {
let attachSheet = UIAlertController(title: nil, message: "File attaching", preferredStyle: .actionSheet)
attachSheet.addAction(UIAlertAction(title: "File", style: .default,handler: { (action) in
let supportedTypes: [UTType] = [UTType.png,UTType.jpeg]
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
documentPicker.shouldShowFileExtensions = true
self.present(documentPicker, animated: true, completion: nil)
}))
attachSheet.addAction(UIAlertAction(title: "Photo/Video", style: .default,handler: { (action) in
self.chooseImage()
}))
attachSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(attachSheet, animated: true, completion: nil)
}
then when a user will choose File he will be moved to ordinary directory where I handle his selection:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
var selectedFileData = [String:String]()
let file = urls[0]
do{
let fileData = try Data.init(contentsOf: file.absoluteURL)
selectedFileData["filename"] = file.lastPathComponent
selectedFileData["data"] = fileData.base64EncodedString(options: .lineLength64Characters)
}catch{
print("contents could not be loaded")
}
}
as you can see in scope above I formed special dicionary for storing data before sending it to a server. Here you can also see encoding to Base64.
When the user will press Photo/Video item in alert dialogue he will be moved to gallery for picture selecting:
func chooseImage() {
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImageData = [String:String]()
guard let fileUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL else { return }
print(fileUrl.lastPathComponent)
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
selectedImageData["filename"] = fileUrl.lastPathComponent
selectedImageData["data"] = pickedImage.pngData()?.base64EncodedString(options: .lineLength64Characters)
}
dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
via my method all file content will be encoded to base64 string.
P.S. Also I'm so pleased to #MaticOblak because he showed me the initial point for my research and final solution. His solution also good, but I have managed to solve my problem in way which is more convenient for my project :)
As soon as you have file URL you can use that URL to retrieve the data it contains. When you have the data you can convert it to Base64 and send it to server. You gave no information about how you will send it to server but the rest may look something like this:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
and you would use it as
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
urls.forEach { sendFileWithURL($0) { <#Your code here#> } }
}
To break it down:
To get file data you can use Data(contentsOf: url). This method even works on remote files so you could for instance use an URL of an image link anywhere on internet you have access to. It is important to know that this method will pause your thread which is usually not what you want.
To avoid breaking the current thread we create a new queue using DispatchQueue(label: "DownloadingFileData." + UUID().uuidString). The name of the queue is not very important but can be useful when debugging.
When data is received we convert it to Base64 string using data.base64EncodedString() and this data can then be sent to server. You just need to fill in the TODO: part.
Retrieving your file data can have some errors. Maybe access restriction or file no longer there or no internet connection... This is handled by throwing. If the statement with try fails for any reason then the catch parts executes and you receive an error.
Since all of this is done on background thread it usually makes sense to go back to main thread. This is what the finish function does. If you do not require that you can simply remove it and have:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
completion(nil)
} catch {
completion(error)
}
}
}
There are other things to consider in this approach. For instance you can see if user selects multiple files then each of them will open its own queue and start the process. That means that if user selects multiple files it is possible that at some point many or all of them will be loaded in memory. That may take too much memory and crash your application. It is for you to decide if this approach is fine for you or you wish to serialize the process. The serialization should be very simple with queues. All you need is to have a single one:
private lazy var fileProcessingQueue: DispatchQueue = DispatchQueue(label: "DownloadingFileData.main")
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
fileProcessingQueue.async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
Now one operation will finish before another one starts. But that may only apply for getting file data and conversion to base64 string. If uploading is then done on another thread (Which usually is) then you may still have multiple ongoing requests which may contain all of the data needed to upload.
I am trying to initiate DocumentPicker in my app. I have declared:
var documentPicker: UIDocumentPickerViewController =
UIDocumentPickerViewController(documentTypes:
["public.text"], in: UIDocumentPickerMode.open)
documentPicker.delegate = self
documentPicker.modalPresentationStyle =
UIModalPresentationStyle.fullScreen
self.present(documentPicker, animated: true, completion: nil)
}
and for picked document:
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) {
if controller.documentPickerMode == UIDocumentPickerMode.exportToService {
//do some stuff
}
dismiss(animated: true, completion: nil)
}
However i am doing something wrong here. Whenever i choose a file or close the window I keep getting this error:
viewServiceDidTerminateWithError: Error Domain=_UIViewServiceErrorDomain Code=1 "(null)" UserInfo={Terminated=disconnect method}.
I have tried looking for a solution but i couldn't find anything useful.
EDIT: I have included a couple of print("test") into the documentPicker function to see if it is working. However i get 0 response from the console. Maybe I have implemented documentPicker in a bad way?
I, too, get this error message but not all the time.
I noticed the documentPicker is deprecated. Try this:
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL urls: [URL]) {
if controller.documentPickerMode == UIDocumentPickerMode.exportToService {
//do some stuff
}
}
There's no need to have the dismiss statement. The picker is closed automatically.
Try this and see if it works.
I was surfing some site in safari. it was attachment button. when I clicked on that button. it showed this controller. Can any1 tell me which is this default functionality safari provide. it is dere in various apps too like slack.
its shows icloud/google drive/dropbox all three in one action.
It is UIDocumentPickController. Check the apple developer documentation
let documentPicker: UIDocumentPickerViewController = UIDocumentPickerViewController(documentTypes: ["public.text"], in: UIDocumentPickerMode.import)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = UIModalPresentationStyle.formSheet
self.present(documentPicker, animated: true, completion: nil)
And delegate
// MARK: - UIDocumentPickerDelegate Methods
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
if controller.documentPickerMode == UIDocumentPickerMode.import {
// This is what it should be
// self.newNoteBody.text = String(contentsOfFile: url.path!)
}
}
After adding the code you should check this post too or else you will get an exception. You should go to capabilities & turn iCloud capabilities on. Add icloud containers also there
UIDocumentMenuController is the same as UIDocumentPickerViewController. But its deprecated. check the developer documentation here
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 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)