UIDocumentPickerViewController 适用于 iOS,但不适用于 Mac Catalyst。有没有其他方法可以解决这个问题?顺便说一句,NSOpenPanel 在 Mac Catalyst 上不可用。
4 回答
@UnchartedWorks 的优秀答案中有额外的代码。这是一个更简洁的版本,带有一些选项,可以更方便地复制/粘贴到您的代码中。这适用于 iOS、iPadOS 和 Mac Catalyst(不使用 #if 条件)。
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")
}
}
重要提示:将“com.apple.security.files.user-selected.read-write”(布尔值,设置为 YES)权利添加到您的应用程序的权利文件中,否则当您在 Mac 上打开选择器时它会崩溃。如果您只需要读取权限,则可以改用“com.apple.security.files.user-selected.read”。
示例用法:
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)
}
}
以下示例适用于 Mac Catalyst。如果你想在 iOS 和 Mac Catalyst 上支持 UIDocumentPickerViewController,你应该使用
#if targetEnvironment(macCatalyst)
//code for Mac Catalyst
#endif
如何在 Mac Catalyst 上支持 UIDocumentPickerViewController
//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")
}
}
感谢 Simon,没有他的帮助,我无法解决这个问题。
经过多次尝试,我设法在 Mac Catalyst 上找到了适用于 Xcode 11.3.1 的以下代码。
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) {
// }
// }
}
我使用上面的代码,例如:
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)
}
}
我希望我是有帮助的。
您必须转到 Target -> Signing & Capabilities 并设置文件访问权限。