tauri-apps / plugins-workspace

All of the official Tauri plugins in one place!
https://tauri.app
Apache License 2.0
819 stars 223 forks source link

[feat] [dialog] How to determine whether to open photos picker or files picker on iOS #1596

Open winjeysong opened 1 month ago

winjeysong commented 1 month ago

using open imported from @tauri-apps/plugin-dialog will open files picker on iOS 17.5 simulator. Is there a way to determine whether to open photos picker or files picker? I just want to open photos picker by open.

tauri-plugin-dialog: 2.0.0-beta.11

FabianLars commented 1 month ago

Considering we have the opposite behavior in https://github.com/tauri-apps/plugins-workspace/issues/1578 i have to admit that this is quite confusing to me. Anyway, just wrote this to link to 1578 so that when someone works one of those issues the other one will be looked at & resolved as well.

Thanks for the report.

ecmel commented 1 month ago

From my testing the following opens the file dialog:

await open({ filters: [{ extensions: ["pdf", "txt"], name: "Documents" }]});

but interestingly the following opens photo chooser:

await open({ filters: [{ extensions: ["pdf"], name: "Documents" }]});

Checking the source code, open method can show multiple dialogs depending on the parsing of the filters attribute so both issues are valid. Parsing logic should be fixed.

bjbk commented 1 month ago

Confirmed. Adding the second extension to the extensions array opens the Files app. However, for some reason the target files are greyed out and not selectable.

file = await open({
        multiple: false,
        directory: false,
        filters: [{ name: 'CSV', extensions: ['txt', 'csv'] }]
      })
bjbk commented 1 month ago

I wonder if this may be of use:

PLEASE USE CAUTION WITH THE CODE BELOW, I am not a Swift developer. I was looking to see what could be the issue with parsing the code and so asked Claude AI. I realize that asking AI may be bad form, but sometimes it proves to be useful.

The code is using kUTTagClassMIMEType to create the UTI, but it's passing in the file extension. Instead, should it use kUTTagClassFilenameExtension?

import MobileCoreServices
import Photos
import PhotosUI
import SwiftRs
import Tauri
import UIKit
import WebKit

// ... (previous code remains the same)

class DialogPlugin: Plugin {
  var filePickerController: FilePickerController!
  var pendingInvoke: Invoke? = nil
  var pendingInvokeArgs: FilePickerOptions? = nil

  override init() {
    super.init()
    filePickerController = FilePickerController(self)
  }

  @objc public func showFilePicker(_ invoke: Invoke) throws {
    let args = try invoke.parseArgs(FilePickerOptions.self)

    let parsedTypes = parseFiltersOption(args.filters ?? [])

    pendingInvoke = invoke
    pendingInvokeArgs = args

    DispatchQueue.main.async {
      if #available(iOS 14, *) {
        var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
        configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1

        if !parsedTypes.isEmpty {
          configuration.filter = self.createPHPickerFilter(from: parsedTypes)
        }

        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self.filePickerController
        picker.modalPresentationStyle = .fullScreen
        self.presentViewController(picker)
      } else {
        let picker = UIDocumentPickerViewController(documentTypes: parsedTypes, in: .import)
        picker.delegate = self.filePickerController
        picker.allowsMultipleSelection = args.multiple ?? false
        picker.modalPresentationStyle = .fullScreen
        self.presentViewController(picker)
      }
    }
  }

  private func parseFiltersOption(_ filters: [Filter]) -> [String] {
    var parsedTypes: [String] = []
    for filter in filters {
      for ext in filter.extensions ?? [] {
        if let utType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil)?.takeRetainedValue() as String? {
          parsedTypes.append(utType)
        } else {
          // If we can't get a UTI for the extension, add a wildcard UTI
          parsedTypes.append("public.item")
        }
      }
    }
    // If no valid types were found, allow all file types
    return parsedTypes.isEmpty ? ["public.item"] : parsedTypes
  }

  @available(iOS 14, *)
  private func createPHPickerFilter(from parsedTypes: [String]) -> PHPickerFilter? {
    let filters = parsedTypes.compactMap { UTType($0) }.map { PHPickerFilter.any(of: [$0]) }
    return filters.isEmpty ? nil : PHPickerFilter.any(of: filters)
  }

  private func presentViewController(_ viewControllerToPresent: UIViewController) {
    self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
  }

  // ... (rest of the code remains the same)
}
winjeysong commented 1 month ago

Thank you, guys

ecmel commented 1 month ago
Screenshot 2024-08-07 at 11 51 07

File chooser on a iOS webview asks for chooser type so I think it is best to have different commands/options for choosing the kind of the dialog.

bjbk commented 4 weeks ago

No change when upgrading to RC tauri-plugin-dialog = "2.0.0-rc.0" "@tauri-apps/plugin-dialog": "2.0.0-rc.0"

file = await open({
        multiple: false,
        directory: false,
        filters: [{ name: 'CSV', extensions: ['txt', 'csv'] }]
      })

Dialog comes up but files cannot be selected. (iOS simulator)

image