Skip to content
This repository has been archived by the owner on Apr 1, 2023. It is now read-only.

feat: Open file with custom file extension on iOS #48

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,12 @@ Only available on Android and iOS.

#### PickFilesOptions

| Prop | Type | Description | Default |
| -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| **`types`** | <code>string[]</code> | 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`** | <code>boolean</code> | Whether multiple files may be selected. | <code>false</code> |
| **`readData`** | <code>boolean</code> | Whether to read the file data. | <code>false</code> |
| Prop | Type | Description | Default |
| ---------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| **`types`** | <code>string[]</code> | 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`** | <code>string[]</code> | iOS only. List of custom file name extensions (without leading dot '.'). Necessary in iOS since custom mimetypes are not supported. Example: `['cs2']` | |
| **`multiple`** | <code>boolean</code> | Whether multiple files may be selected. | <code>false</code> |
| **`readData`** | <code>boolean</code> | Whether to read the file data. | <code>false</code> |


#### PickMediaOptions
Expand Down
12 changes: 12 additions & 0 deletions ios/Plugin/FilePicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Photos
import Capacitor
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers

@objc public class FilePicker: NSObject {
private var plugin: FilePickerPlugin?
Expand Down Expand Up @@ -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
Expand Down
53 changes: 49 additions & 4 deletions ios/Plugin/FilePickerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation
import Capacitor
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers

/**
* Please read the Capacitor iOS Plugin Development Guide
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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) ?? ""
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down