Implement Document Picker in swift (iOS)
Update for iOS 14: You do not need any capabilities. Just create a UIDocumentPickerViewController
with the appropriate types, implement the delegate, and you are done.
More info in this answer. Code from there:
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)
}
From your project's capabilities, enable both the iCloud
and the Key-Sharing
.
Import MobileCoreServices
in your class and then extend the following three classes inside your UIViewController
:
UIDocumentMenuDelegate,UIDocumentPickerDelegate,UINavigationControllerDelegate
Implement the following functions:
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let myURL = urls.first else {
return
}
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 do you 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
kUTTypePNG
kUTTypeJPEG
...
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.
How to add file picker to the app on iOS 14+ and lower
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.
iOS Document Picker crashes when picking a PDF on a real device
The issue there is that you are using the wrong mode when defining the type of file transfer operation used by the document picker. .open
is used to open an external file located outside your app’s sandbox. What you need is to use .import
it will create a temporary file that will allow you to load its contents or move/copy the file to a permanent location. If it doesn't solve you issue check this post on how to implement your DocumentPickerViewController coordinator
let controller = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF)], in: .import)
Swift 5 - documentPicker not called after a file was selected
You need to retain the document-picker
delegate (in this case, the instance of ImportMenuController).
This fixes it for you.
let importer = ImportMenuController()
var body: some View {
ZStack() {
Button("Select files to sync") {
importer.selectFile()
}
}
SwiftUI - Use File Picker to Open a file belonging to another IOS Application, then access that file directly
So just for the archive, it turns out that my original Document Picker code was working - and was working correctly - just as I had provided it.
The problem turned out to be the way I was handling the response.
My original wrong code was:
do {
data1 = try NSData.init(
contentsOf: URL.init(fileURLWithPath: fileURL, isDirectory: true)
) as Data
}
catch {
print("Error \(error)")
}
and that was the problem. Turns out that to get the data stream from the file, all I needed to do was:
do {
data1 = try Data(contentsOf: fileURL!)
}
catch {
print("Error \(error)")
}
That was it! When I did that, I was able to read the contents of the file, and correctly upload them via POST to my server.
Thanks to all who read my question, and to Prafulla for their response!
Select two documents using iOS Document Picker in Swift
Selecting multiple files will be possible with iOS11 by setting “allowMultipleSelection” Boolean to true for your UidocumentPicker https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller/2902365-allowsmultipleselection
How to open the Document files e.g(.pdf,.doc,.docx) in ios mobile when a button action using swift3.0?
Swift 3*, 4*,
To open document and select any document, you are using UIDocumentPickerViewController
then all documents presented in your iCloud, Files and in Google Drive will be shown if Google Drive is connected in user device. Then selected document need to download in your app and from there you can show it in WKWebView
,
@IBAction func uploadNewResumeAction(_ sender: Any) {
/* let documentPicker = UIDocumentPickerViewController(documentTypes: ["com.apple.iwork.pages.pages", "com.apple.iwork.numbers.numbers", "com.apple.iwork.keynote.key","public.image", "com.apple.application", "public.item","public.data", "public.content", "public.audiovisual-content", "public.movie", "public.audiovisual-content", "public.video", "public.audio", "public.text", "public.data", "public.zip-archive", "com.pkware.zip-archive", "public.composite-content", "public.text"], in: .import) */
let documentPicker = UIDocumentPickerViewController(documentTypes: ["public.text", "com.apple.iwork.pages.pages", "public.data"], in: .import)
documentPicker.delegate = self
present(documentPicker, animated: true, completion: nil)
}
extension YourViewController: UIDocumentPickerDelegate{
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
let cico = url as URL
print(cico)
print(url)
print(url.lastPathComponent)
print(url.pathExtension)
}
}
Note: If you intend to select all files the you have to use following code:
let documentPicker = UIDocumentPickerViewController(documentTypes: ["com.apple.iwork.pages.pages", "com.apple.iwork.numbers.numbers", "com.apple.iwork.keynote.key","public.image", "com.apple.application", "public.item","public.data", "public.content", "public.audiovisual-content", "public.movie", "public.audiovisual-content", "public.video", "public.audio", "public.text", "public.data", "public.zip-archive", "com.pkware.zip-archive", "public.composite-content", "public.text"], in: .import)
In your action method.
Does using UIDocumentPickerViewController need iCloud capability to be enabled?
No, you do not need iCloud capability to be enabled for UIDocumentPickerViewController
. It doesn't require any capabilities actually.
Related Topics
Passing Data to View Controllers That Are Embedded in Container Views
iOS Nsdate() Returns Incorrect Time
Filemanager Cannot Find Audio File
Error: Initializer for Conditional Binding Must Have Optional Type, Not 'String'
How to Change Wkwebview or Uiwebview Default Font
Using Nsuserdefaults with Xcode 8 and iOS 10
How to Add Images for Different Screen Size from Assets.Xcassets in Xcode 8
How to Rotate Orientation Programmatically in Swift
Trying to Set Only Time in Uidatepicker in Swift 2.0
Cannot Assign a Value of Type "String" to Type "Uilabel" in Swift
Swift: Can't Get Nsdate Dateformatter Right
Realmswift: Convert Results to Swift Array
How to Manually Deprecate Members
Using Audiobufferlist with Swift
Difference Between Dispatchqueue.Main.Async and Dispatchqueue.Main.Sync