168 lines
4.4 KiB
Swift
168 lines
4.4 KiB
Swift
// UIDocumentPickerViewController is unavailable on tvOS
|
|
#if os(iOS)
|
|
import ExpoModulesCore
|
|
import MobileCoreServices
|
|
import UIKit
|
|
import UniformTypeIdentifiers
|
|
|
|
internal protocol FilePickingResultHandler {
|
|
func didPickFileAt(url: URL)
|
|
func didPickDirectoryAt(url: URL)
|
|
func didCancelPicking()
|
|
}
|
|
|
|
internal struct FilePickingContext {
|
|
let promise: Promise
|
|
let initialUri: URL?
|
|
let mimeType: String?
|
|
let isDirectory: Bool
|
|
let delegate: FilePickingDelegate
|
|
var pickedUrl: URL?
|
|
}
|
|
|
|
internal class FilePickingDelegate: NSObject, UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate {
|
|
private let resultHandler: FilePickingResultHandler
|
|
private let isDirectory: Bool
|
|
private weak var pickingHandler: FilePickingHandler?
|
|
|
|
init(resultHandler: FilePickingResultHandler, isDirectory: Bool = false) {
|
|
self.resultHandler = resultHandler
|
|
self.isDirectory = isDirectory
|
|
self.pickingHandler = resultHandler as? FilePickingHandler
|
|
}
|
|
|
|
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
|
guard let url = urls.first else {
|
|
self.resultHandler.didCancelPicking()
|
|
return
|
|
}
|
|
|
|
if isDirectory {
|
|
// For directory access, we need to start accessing the security-scoped resource
|
|
let didStartAccessing = url.startAccessingSecurityScopedResource()
|
|
if didStartAccessing {
|
|
// Store the picked URL for proper cleanup
|
|
if let pickingHandler = pickingHandler {
|
|
pickingHandler.filePickingContext?.pickedUrl = url
|
|
}
|
|
self.resultHandler.didPickDirectoryAt(url: url)
|
|
} else {
|
|
// If we can't access the directory, treat as cancellation
|
|
self.resultHandler.didCancelPicking()
|
|
}
|
|
} else {
|
|
self.resultHandler.didPickFileAt(url: url)
|
|
}
|
|
}
|
|
|
|
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
|
self.resultHandler.didCancelPicking()
|
|
}
|
|
|
|
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
self.resultHandler.didCancelPicking()
|
|
}
|
|
}
|
|
|
|
internal func createFilePicker(initialUri: URL?, mimeType: String?) -> UIDocumentPickerViewController {
|
|
if #available(iOS 14.0, *) {
|
|
let utTypes: [UTType]
|
|
if let mimeType = mimeType {
|
|
if let utType = UTType(mimeType: mimeType) {
|
|
utTypes = [utType]
|
|
} else {
|
|
utTypes = [UTType.item]
|
|
}
|
|
} else {
|
|
utTypes = [UTType.item]
|
|
}
|
|
|
|
let picker = UIDocumentPickerViewController(forOpeningContentTypes: utTypes, asCopy: true)
|
|
|
|
if let initialUri = initialUri {
|
|
picker.directoryURL = initialUri
|
|
}
|
|
|
|
return picker
|
|
}
|
|
|
|
let utiTypes: [String]
|
|
if let mimeType = mimeType {
|
|
utiTypes = [toUTI(mimeType: mimeType)]
|
|
} else {
|
|
utiTypes = [kUTTypeItem as String]
|
|
}
|
|
|
|
let picker = UIDocumentPickerViewController(documentTypes: utiTypes, in: .import)
|
|
|
|
if let initialUri = initialUri {
|
|
picker.directoryURL = initialUri
|
|
}
|
|
|
|
return picker
|
|
}
|
|
|
|
internal func createDirectoryPicker(initialUri: URL?) -> UIDocumentPickerViewController {
|
|
if #available(iOS 14.0, *) {
|
|
// Use UTType.folder for directory access as per Apple's documentation
|
|
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.folder], asCopy: false)
|
|
if let initialUri = initialUri {
|
|
picker.directoryURL = initialUri
|
|
}
|
|
return picker
|
|
}
|
|
// For iOS 13 and earlier, use kUTTypeFolder
|
|
let picker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open)
|
|
if let initialUri = initialUri {
|
|
picker.directoryURL = initialUri
|
|
}
|
|
return picker
|
|
}
|
|
|
|
@available(iOS 14.0, *)
|
|
private func toUTType(mimeType: String) -> UTType? {
|
|
switch mimeType {
|
|
case "*/*":
|
|
return UTType.item
|
|
case "image/*":
|
|
return UTType.image
|
|
case "video/*":
|
|
return UTType.movie
|
|
case "audio/*":
|
|
return UTType.audio
|
|
case "text/*":
|
|
return UTType.text
|
|
default:
|
|
return UTType(mimeType: mimeType)
|
|
}
|
|
}
|
|
|
|
private func toUTI(mimeType: String) -> String {
|
|
var uti: CFString
|
|
|
|
switch mimeType {
|
|
case "*/*":
|
|
uti = kUTTypeItem
|
|
case "image/*":
|
|
uti = kUTTypeImage
|
|
case "video/*":
|
|
uti = kUTTypeVideo
|
|
case "audio/*":
|
|
uti = kUTTypeAudio
|
|
case "text/*":
|
|
uti = kUTTypeText
|
|
default:
|
|
if let ref = UTTypeCreatePreferredIdentifierForTag(
|
|
kUTTagClassMIMEType,
|
|
mimeType as CFString,
|
|
nil
|
|
)?.takeRetainedValue() {
|
|
uti = ref
|
|
} else {
|
|
uti = kUTTypeItem
|
|
}
|
|
}
|
|
return uti as String
|
|
}
|
|
#endif
|