diff --git a/README.md b/README.md index 869ace5..953856e 100644 --- a/README.md +++ b/README.md @@ -254,11 +254,12 @@ Only available on Android and iOS. #### PickFilesOptions -| Prop | Type | Description | Default | -| -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| **`types`** | string[] | List of accepted file types. Look at [IANA Media Types](https://www.iana.org/assignments/media-types/media-types.xhtml) for a complete list of standard media types. This option cannot be used with `multiple: true` on Android. | | -| **`multiple`** | boolean | Whether multiple files may be selected. | false | -| **`readData`** | boolean | Whether to read the file data. | false | +| Prop | Type | Description | Default | +| ---------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| **`types`** | string[] | List of accepted file types. Look at [IANA Media Types](https://www.iana.org/assignments/media-types/media-types.xhtml) for a complete list of standard media types. This option cannot be used with `multiple: true` on Android. | | +| **`customExtensions`** | string[] | iOS only. List of custom file name extensions (without leading dot '.'). Necessary in iOS since custom mimetypes are not supported. Example: `['cs2']` | | +| **`multiple`** | boolean | Whether multiple files may be selected. | false | +| **`readData`** | boolean | Whether to read the file data. | false | #### PickMediaOptions diff --git a/ios/Plugin/FilePicker.swift b/ios/Plugin/FilePicker.swift index 3611631..effb768 100644 --- a/ios/Plugin/FilePicker.swift +++ b/ios/Plugin/FilePicker.swift @@ -4,6 +4,7 @@ import Photos import Capacitor import UIKit import MobileCoreServices +import UniformTypeIdentifiers @objc public class FilePicker: NSObject { private var plugin: FilePickerPlugin? @@ -96,6 +97,17 @@ import MobileCoreServices } } } + + @available(iOS 14.0, *) + public func openDocumentPickerWithFileExtensions(multiple: Bool, documentTypes: [UTType]) { + DispatchQueue.main.async { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: documentTypes) + documentPicker.delegate = self + documentPicker.allowsMultipleSelection = multiple + documentPicker.modalPresentationStyle = .fullScreen + self.plugin?.bridge?.viewController?.present(documentPicker, animated: true, completion: nil) + } + } public func getPathFromUrl(_ url: URL) -> String { return url.absoluteString diff --git a/ios/Plugin/FilePickerPlugin.swift b/ios/Plugin/FilePickerPlugin.swift index b5f3320..0f5d170 100644 --- a/ios/Plugin/FilePickerPlugin.swift +++ b/ios/Plugin/FilePickerPlugin.swift @@ -2,6 +2,7 @@ import Foundation import Capacitor import UIKit import MobileCoreServices +import UniformTypeIdentifiers /** * Please read the Capacitor iOS Plugin Development Guide @@ -53,10 +54,20 @@ public class FilePickerPlugin: CAPPlugin { let multiple = call.getBool("multiple", false) let types = call.getArray("types", String.self) ?? [] - let parsedTypes = parseTypesOption(types) - let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes - - implementation?.openDocumentPicker(multiple: multiple, documentTypes: documentTypes) + let fileExtensions = call.getArray("customExtensions", String.self) ?? [] + if #available(iOS 14.0, *) { + let parsedTypes = parseTypesOptionsToUTTypes(types) + let parsedExtensions = parseCustomExtensions(fileExtensions) + let concatenatedTypes = parsedTypes + parsedExtensions + let documentTypes = concatenatedTypes.isEmpty ? [.data] : concatenatedTypes + implementation?.openDocumentPickerWithFileExtensions(multiple: multiple, documentTypes: documentTypes) + } else { + // Fallback on earlier versions + let parsedTypes = parseTypesOption(types) + let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes + + implementation?.openDocumentPicker(multiple: multiple, documentTypes: documentTypes) + } } @objc func pickImages(_ call: CAPPluginCall) { @@ -94,6 +105,30 @@ public class FilePickerPlugin: CAPPlugin { return parsedTypes } + @available(iOS 14.0, *) + @objc func parseTypesOptionsToUTTypes(_ types: [String]) -> [UTType] { + var parsedTypes: [UTType] = [] + for (_, type) in types.enumerated() { + guard let utType: UTType = UTType(mimeType: type) else { + continue + } + parsedTypes.append(utType) + } + return parsedTypes + } + + @available(iOS 14.0, *) + @objc func parseCustomExtensions(_ extensions: [String]) -> [UTType] { + var parsedExtensions: [UTType] = [] + for (_, exten) in extensions.enumerated() { + guard let utType: UTType = UTType(filenameExtension: exten) else { + continue + } + parsedExtensions.append(utType) + } + return parsedExtensions + } + @objc func handleDocumentPickerResult(urls: [URL]?, error: String?) { guard let savedCall = savedCall else { return @@ -107,9 +142,18 @@ public class FilePickerPlugin: CAPPlugin { return } let readData = savedCall.getBool("readData", false) + for (url) in urls { + guard url.startAccessingSecurityScopedResource() else { + return + } + } do { var result = JSObject() let filesResult = try urls.map {(url: URL) -> JSObject in + guard url.startAccessingSecurityScopedResource() else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "File access denied"]) + throw error + } var file = JSObject() if readData == true { file["data"] = try implementation?.getDataFromUrl(url) ?? "" @@ -130,6 +174,7 @@ public class FilePickerPlugin: CAPPlugin { file["name"] = implementation?.getNameFromUrl(url) ?? "" file["path"] = implementation?.getPathFromUrl(url) ?? "" file["size"] = try implementation?.getSizeFromUrl(url) ?? -1 + url.stopAccessingSecurityScopedResource() return file } result["files"] = filesResult diff --git a/src/definitions.ts b/src/definitions.ts index b012481..acc7d66 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -81,6 +81,12 @@ export interface PickFilesOptions { * @example ['image/png', 'application/pdf'] */ types?: string[]; + /** + * iOS only. List of custom file name extensions (without leading dot '.'). Necessary in iOS since custom mimetypes are not supported. + * + * Example: `['cs2']` + */ + customExtensions?: string[]; /** * Whether multiple files may be selected. *