From 90a5a34aa5d5ba8a575d52fbadad362542d256ca Mon Sep 17 00:00:00 2001 From: Gabriel Dantas Date: Thu, 6 Oct 2022 16:26:30 -0300 Subject: [PATCH 1/4] add customExtensions picking capacity for iOS --- README.md | 9 ++++++ ios/Plugin/FilePicker.swift | 12 +++++++ ios/Plugin/FilePickerPlugin.swift | 52 ++++++++++++++++++++++++++++--- src/definitions.ts | 6 ++++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 869ace5..6582a59 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ Only available on Android and iOS. #### PickFilesOptions +<<<<<<< HEAD | 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. | | @@ -300,6 +301,14 @@ Only available on Android and iOS. #### PickVideosResult PickMediaResult +======= +| Prop | Type | Description | +| -------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`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. Example: `['image/png', 'application/pdf']` | +| **`customExtensions`** | string[] | List of custom extensions. Necessary in iOS since the mimetype alone is not enought Example: `['cs2']` | +| **`multiple`** | boolean | Whether multiple files may be selected. Default: `false` | +| **`readData`** | boolean | Whether to read the file data. Default: `false` | +>>>>>>> bd956c9 (add customExtensions picking capacity for iOS) diff --git a/ios/Plugin/FilePicker.swift b/ios/Plugin/FilePicker.swift index 3611631..b2a0f20 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 updatedOpenDocumentPicker(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..b3a4057 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 = newParseTypesOption(types) + let parsedExtensions = parseCustomExtensions(fileExtensions) + let concatenatedTypes = parsedTypes + parsedExtensions + let documentTypes = concatenatedTypes.isEmpty ? [.jpeg] : concatenatedTypes + implementation?.updatedOpenDocumentPicker(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 newParseTypesOption(_ 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,17 @@ 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 { + return + } var file = JSObject() if readData == true { file["data"] = try implementation?.getDataFromUrl(url) ?? "" @@ -130,6 +173,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..09f4527 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -81,6 +81,12 @@ export interface PickFilesOptions { * @example ['image/png', 'application/pdf'] */ types?: string[]; + /** + * List of custom extensions. Necessary in iOS since the mimetype alone is not enought + * + * Example: `['cs2']` + */ + customExtensions?: string[]; /** * Whether multiple files may be selected. * From c16bfd952b5e0614bd9026088077c5058c01989b Mon Sep 17 00:00:00 2001 From: Gabriel Dantas Date: Tue, 25 Oct 2022 08:22:23 -0300 Subject: [PATCH 2/4] Update src/definitions.ts Co-authored-by: Daniel Kasper <13744542+de-dan@users.noreply.github.com> --- src/definitions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/definitions.ts b/src/definitions.ts index 09f4527..acc7d66 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -82,7 +82,7 @@ export interface PickFilesOptions { */ types?: string[]; /** - * List of custom extensions. Necessary in iOS since the mimetype alone is not enought + * iOS only. List of custom file name extensions (without leading dot '.'). Necessary in iOS since custom mimetypes are not supported. * * Example: `['cs2']` */ From eed12b60030143d268b262cdab1aa4c2dd945e3f Mon Sep 17 00:00:00 2001 From: Gabriel Dantas Date: Tue, 25 Oct 2022 10:51:27 -0300 Subject: [PATCH 3/4] improve function naming, fix empty types fallback, fix fallback to startAccessingSecurityScopedResource --- README.md | 9 +++++++++ ios/Plugin/FilePicker.swift | 2 +- ios/Plugin/FilePickerPlugin.swift | 19 ++++++++++--------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6582a59..1795e3e 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ Only available on Android and iOS. #### PickFilesOptions +<<<<<<< HEAD <<<<<<< HEAD | Prop | Type | Description | Default | | -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | @@ -309,6 +310,14 @@ Only available on Android and iOS. | **`multiple`** | boolean | Whether multiple files may be selected. Default: `false` | | **`readData`** | boolean | Whether to read the file data. Default: `false` | >>>>>>> bd956c9 (add customExtensions picking capacity for iOS) +======= +| Prop | Type | Description | +| ---------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`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. Example: `['image/png', 'application/pdf']` | +| **`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. Default: `false` | +| **`readData`** | boolean | Whether to read the file data. Default: `false` | +>>>>>>> e31b1e2 (improve function naming, fix empty types fallback, fix fallback to startAccessingSecurityScopedResource) diff --git a/ios/Plugin/FilePicker.swift b/ios/Plugin/FilePicker.swift index b2a0f20..effb768 100644 --- a/ios/Plugin/FilePicker.swift +++ b/ios/Plugin/FilePicker.swift @@ -99,7 +99,7 @@ import UniformTypeIdentifiers } @available(iOS 14.0, *) - public func updatedOpenDocumentPicker(multiple: Bool, documentTypes: [UTType]) { + public func openDocumentPickerWithFileExtensions(multiple: Bool, documentTypes: [UTType]) { DispatchQueue.main.async { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: documentTypes) documentPicker.delegate = self diff --git a/ios/Plugin/FilePickerPlugin.swift b/ios/Plugin/FilePickerPlugin.swift index b3a4057..0f5d170 100644 --- a/ios/Plugin/FilePickerPlugin.swift +++ b/ios/Plugin/FilePickerPlugin.swift @@ -56,11 +56,11 @@ public class FilePickerPlugin: CAPPlugin { let types = call.getArray("types", String.self) ?? [] let fileExtensions = call.getArray("customExtensions", String.self) ?? [] if #available(iOS 14.0, *) { - let parsedTypes = newParseTypesOption(types) + let parsedTypes = parseTypesOptionsToUTTypes(types) let parsedExtensions = parseCustomExtensions(fileExtensions) let concatenatedTypes = parsedTypes + parsedExtensions - let documentTypes = concatenatedTypes.isEmpty ? [.jpeg] : concatenatedTypes - implementation?.updatedOpenDocumentPicker(multiple: multiple, documentTypes: documentTypes) + let documentTypes = concatenatedTypes.isEmpty ? [.data] : concatenatedTypes + implementation?.openDocumentPickerWithFileExtensions(multiple: multiple, documentTypes: documentTypes) } else { // Fallback on earlier versions let parsedTypes = parseTypesOption(types) @@ -106,7 +106,7 @@ public class FilePickerPlugin: CAPPlugin { } @available(iOS 14.0, *) - @objc func newParseTypesOption(_ types: [String]) -> [UTType] { + @objc func parseTypesOptionsToUTTypes(_ types: [String]) -> [UTType] { var parsedTypes: [UTType] = [] for (_, type) in types.enumerated() { guard let utType: UTType = UTType(mimeType: type) else { @@ -143,15 +143,16 @@ public class FilePickerPlugin: CAPPlugin { } let readData = savedCall.getBool("readData", false) for (url) in urls { - guard url.startAccessingSecurityScopedResource() else { - return - } - } + guard url.startAccessingSecurityScopedResource() else { + return + } + } do { var result = JSObject() let filesResult = try urls.map {(url: URL) -> JSObject in guard url.startAccessingSecurityScopedResource() else { - return + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "File access denied"]) + throw error } var file = JSObject() if readData == true { From a49c6a3aa3c089989119095b6e7bd564c606c7f4 Mon Sep 17 00:00:00 2001 From: Gabriel Dantas Date: Thu, 16 Feb 2023 18:29:31 -0300 Subject: [PATCH 4/4] solve rebase conflicts --- README.md | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1795e3e..953856e 100644 --- a/README.md +++ b/README.md @@ -254,13 +254,12 @@ Only available on Android and iOS. #### PickFilesOptions -<<<<<<< HEAD -<<<<<<< HEAD -| 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 @@ -302,22 +301,6 @@ Only available on Android and iOS. #### PickVideosResult PickMediaResult -======= -| Prop | Type | Description | -| -------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`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. Example: `['image/png', 'application/pdf']` | -| **`customExtensions`** | string[] | List of custom extensions. Necessary in iOS since the mimetype alone is not enought Example: `['cs2']` | -| **`multiple`** | boolean | Whether multiple files may be selected. Default: `false` | -| **`readData`** | boolean | Whether to read the file data. Default: `false` | ->>>>>>> bd956c9 (add customExtensions picking capacity for iOS) -======= -| Prop | Type | Description | -| ---------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`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. Example: `['image/png', 'application/pdf']` | -| **`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. Default: `false` | -| **`readData`** | boolean | Whether to read the file data. Default: `false` | ->>>>>>> e31b1e2 (improve function naming, fix empty types fallback, fix fallback to startAccessingSecurityScopedResource)