You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tauri-plugins-workspace/plugins/dialog/ios/Sources/FilePickerController.swift

229 lines
7.4 KiB

import UIKit
import MobileCoreServices
import PhotosUI
import Photos
import Tauri
public class FilePickerController: NSObject {
var plugin: DialogPlugin
init(_ dialogPlugin: DialogPlugin) {
plugin = dialogPlugin
}
private func dismissViewController(_ viewControllerToPresent: UIViewController, completion: (() -> Void)? = nil) {
viewControllerToPresent.dismiss(animated: true, completion: completion)
}
public func getModifiedAtFromUrl(_ url: URL) -> Int? {
do {
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
if let modifiedDateInSec = (attributes[.modificationDate] as? Date)?.timeIntervalSince1970 {
return Int(modifiedDateInSec * 1000.0)
} else {
return nil
}
} catch let error as NSError {
Logger.error("getModifiedAtFromUrl failed", error.localizedDescription)
return nil
}
}
public func getMimeTypeFromUrl(_ url: URL) -> String {
let fileExtension = url.pathExtension as CFString
guard let extUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, nil)?.takeUnretainedValue() else {
return ""
}
guard let mimeUTI = UTTypeCopyPreferredTagWithClass(extUTI, kUTTagClassMIMEType) else {
return ""
}
return mimeUTI.takeRetainedValue() as String
}
public func getSizeFromUrl(_ url: URL) throws -> Int {
let values = try url.resourceValues(forKeys: [.fileSizeKey])
return values.fileSize ?? 0
}
public func getVideoDuration(_ url: URL) -> Int {
let asset = AVAsset(url: url)
let duration = asset.duration
let durationTime = CMTimeGetSeconds(duration)
return Int(round(durationTime))
}
public func getImageDimensions(_ url: URL) -> (Int?, Int?) {
if let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) {
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {
return getHeightAndWidthFromImageProperties(imageProperties)
}
}
return (nil, nil)
}
public func getVideoDimensions(_ url: URL) -> (Int?, Int?) {
guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return (nil, nil) }
let size = track.naturalSize.applying(track.preferredTransform)
let height = abs(Int(size.height))
let width = abs(Int(size.width))
return (height, width)
}
private func getHeightAndWidthFromImageProperties(_ properties: [NSObject: AnyObject]) -> (Int?, Int?) {
let width = properties[kCGImagePropertyPixelWidth] as? Int
let height = properties[kCGImagePropertyPixelHeight] as? Int
let orientation = properties[kCGImagePropertyOrientation] as? Int ?? UIImage.Orientation.up.rawValue
switch orientation {
case UIImage.Orientation.left.rawValue, UIImage.Orientation.right.rawValue, UIImage.Orientation.leftMirrored.rawValue, UIImage.Orientation.rightMirrored.rawValue:
return (width, height)
default:
return (height, width)
}
}
private func getFileUrlByPath(_ path: String) -> URL? {
guard let url = URL.init(string: path) else {
return nil
}
if FileManager.default.fileExists(atPath: url.path) {
return url
} else {
return nil
}
}
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
directory = cachesDirectory
}
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
do {
try deleteFile(targetUrl)
}
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
return targetUrl
}
private func deleteFile(_ url: URL) throws {
if FileManager.default.fileExists(atPath: url.path) {
try FileManager.default.removeItem(atPath: url.path)
}
}
}
extension FilePickerController: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
let temporaryUrls = try urls.map { try saveTemporaryFile($0) }
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
} catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
}
}
public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
self.plugin.onFilePickerEvent(.cancelled)
}
}
extension FilePickerController: UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate {
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismissViewController(picker)
self.plugin.onFilePickerEvent(.cancelled)
}
public func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
self.plugin.onFilePickerEvent(.cancelled)
}
public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.plugin.onFilePickerEvent(.cancelled)
}
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
dismissViewController(picker) {
if let url = info[.mediaURL] as? URL {
do {
let temporaryUrl = try self.saveTemporaryFile(url)
self.plugin.onFilePickerEvent(.selected([temporaryUrl]))
} catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
}
} else {
self.plugin.onFilePickerEvent(.cancelled)
}
}
}
}
@available(iOS 14, *)
extension FilePickerController: PHPickerViewControllerDelegate {
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismissViewController(picker)
if results.first == nil {
self.plugin.onFilePickerEvent(.cancelled)
return
}
var temporaryUrls: [URL] = []
var errorMessage: String?
let dispatchGroup = DispatchGroup()
for result in results {
if errorMessage != nil {
break
}
if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
dispatchGroup.enter()
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier, completionHandler: { url, error in
defer {
dispatchGroup.leave()
}
if let error = error {
errorMessage = error.localizedDescription
return
}
guard let url = url else {
errorMessage = "Unknown error"
return
}
do {
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
errorMessage = "Failed to create a temporary copy of the file"
}
})
} else if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
dispatchGroup.enter()
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier, completionHandler: { url, error in
defer {
dispatchGroup.leave()
}
if let error = error {
errorMessage = error.localizedDescription
return
}
guard let url = url else {
errorMessage = "Unknown error"
return
}
do {
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
errorMessage = "Failed to create a temporary copy of the file"
}
})
} else {
errorMessage = "Unsupported file type identifier"
}
}
dispatchGroup.notify(queue: .main) {
if let errorMessage = errorMessage {
self.plugin.onFilePickerEvent(.error(errorMessage))
return
}
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
}
}
}