问题
UIDocumentPickerViewController works on iOS but not on Mac Catalyst. Is there any alternatives to workaround this issue? BTW, NSOpenPanel is unavailable on Mac Catalyst.
回答1:
There's extra code in @UnchartedWorks' excellent answer. Here's a cleaner version with some options, more copy/paste-able into your code. This works on iOS, iPadOS, and Mac Catalyst (without using a #if conditional).
import Foundation
import SwiftUI
import MobileCoreServices
/// A wrapper for a UIDocumentPickerViewController that acts as a delegate and passes the selected file to a callback
///
/// DocumentPicker also sets allowsMultipleSelection to `false`.
final class DocumentPicker: NSObject {
/// The types of documents to show in the picker
let types: [String]
/// The callback to call with the selected document URLs
let callback: ([URL]) -> ()
/// Should the user be allowed to select more than one item?
let allowsMultipleSelection: Bool
/// Creates a DocumentPicker, defaulting to selecting folders and allowing only one selection
init(for types: [String] = [String(kUTTypeFolder)],
allowsMultipleSelection: Bool = false,
_ callback: @escaping ([URL]) -> () = { _ in }) {
self.types = types
self.allowsMultipleSelection = allowsMultipleSelection
self.callback = callback
}
/// Returns the view controller that must be presented to display the picker
lazy var viewController: UIDocumentPickerViewController = {
let vc = UIDocumentPickerViewController(documentTypes: types, in: .open)
vc.delegate = self
vc.allowsMultipleSelection = self.allowsMultipleSelection
return vc
}()
}
extension DocumentPicker: UIDocumentPickerDelegate {
/// Delegate method that's called when the user selects one or more documents or folders
///
/// This method calls the provided callback with the URLs of the selected documents or folders.
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
callback(urls)
}
/// Delegate method that's called when the user cancels or otherwise dismisses the picker
///
/// Does nothing but close the picker.
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true, completion: nil)
print("cancelled")
}
}
IMPORTANT: Add the "com.apple.security.files.user-selected.read-write" (Boolean, set to YES) entitlement to your app's entitlements file or it will crash when you open the picker on the Mac. If you only need read access, you can use "com.apple.security.files.user-selected.read" instead.
Sample usage:
struct ContentView: View {
/// The view controller for the sheet that lets the user select the project support folder
///
/// Yes, I said "view controller" - we need to go old school for Catalyst and present a view controller from the root view controller manually.
@State var filePicker: DocumentPicker
init() {
// Setting filePicker like this lets us keep DocumentPicker in the view
// layer (instead of a model or view model), lets SwiftUI hang onto
// the reference to it, and lets you specify another function to
// call (e.g. one from a view model) using info passed into your
// init method.
_filePicker = State(initialValue: DocumentPicker({urls in
print(urls)
}))
}
var body: some View {
Button("Pick a folder") {
self.presentDocumentPicker()
}
}
/// Presents the document picker from the root view controller
///
/// This is required on Catalyst but works on iOS and iPadOS too, so we do it this way instead of in a UIViewControllerRepresentable
func presentDocumentPicker() {
let viewController = UIApplication.shared.windows[0].rootViewController!
let controller = self.filePicker.viewController
viewController.present(controller, animated: true)
}
}
回答2:
The following example is for Mac Catalyst. If you want to support UIDocumentPickerViewController on iOS and Mac Catalyst, you should use
#if targetEnvironment(macCatalyst)
//code for Mac Catalyst
#endif
How to support UIDocumentPickerViewController on Mac Catalyst
//SceneDelegate.swift
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var picker = DocumentPicker()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
window.rootViewController?.present(picker.viewController, animated: true)
}
}
}
//ContentView.swift
final class DocumentPicker: NSObject, UIViewControllerRepresentable {
typealias UIViewControllerType = UIDocumentPickerViewController
lazy var viewController: UIDocumentPickerViewController = {
let vc = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .open)
vc.delegate = self
return vc
}()
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
viewController.delegate = self
return viewController
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<DocumentPicker>) {
}
}
extension DocumentPicker: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(urls)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true, completion: nil)
print("cancelled")
}
}
Thanks for Simon, without his help, I can't solve this issue.
回答3:
After many attempts, I managed to find the code below that works well with Xcode 11.3.1 on Mac Catalyst.
import SwiftUI
final class DocumentPicker: NSObject, UIViewControllerRepresentable, ObservableObject {
typealias UIViewControllerType = UIDocumentPickerViewController
@Published var urlsPicked = [URL]()
lazy var viewController:UIDocumentPickerViewController = {
// For picked only folder
let vc = UIDocumentPickerViewController(documentTypes: ["public.folder"], in: .open)
// For picked every document
// let vc = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .open)
// For picked only images
// let vc = UIDocumentPickerViewController(documentTypes: ["public.image"], in: .open)
vc.allowsMultipleSelection = false
// vc.accessibilityElements = [kFolderActionCode]
// vc.shouldShowFileExtensions = true
vc.delegate = self
return vc
}()
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
viewController.delegate = self
return viewController
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<DocumentPicker>) {
}
}
extension DocumentPicker: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
urlsPicked = urls
print("DocumentPicker geoFolder.geoFolderPath: \(urlsPicked[0].path)")
}
// func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
// controller.dismiss(animated: true) {
// }
// }
}
and I use the code above, for example with:
import SwiftUI
struct ContentView: View {
@ObservedObject var picker = DocumentPicker()
@State private var urlPick = ""
var body: some View {
HStack {
urlPickedFoRTextField()
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
Button(action: {
#if targetEnvironment(macCatalyst)
let viewController = UIApplication.shared.windows[0].rootViewController!
viewController.present(self.picker.viewController, animated: true)
self.picker.objectWillChange.send()
#endif
print("Hai premuto il pulsante per determinare il path della GeoFolder")
}) {
Image(systemName: "square.and.arrow.up")
}
}
.padding()
}
private func urlPickedFoRTextField() -> some View {
if picker.urlsPicked.count > 0 {
DispatchQueue.main.async {
self.urlPick = self.picker.urlsPicked[0].path
}
}
return TextField("", text: $urlPick)
}
}
I hope I was helpful.
来源:https://stackoverflow.com/questions/58792307/uidocumentpickerviewcontroller-doesnt-show-any-contents-on-mac-catalyst