UIDocumentPickerViewController在iOS上有效,但在Mac Catalyst上无效。是否有其他替代方法可以解决此问题?顺便说一句,Mac Catalyst上没有NSOpenPanel。
答案 0 :(得分:3)
@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)
}
}
答案 1 :(得分:1)
以下示例适用于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")
}
}
感谢西蒙,没有他的帮助,我无法解决这个问题。
答案 2 :(得分:1)
经过多次尝试,我设法找到了下面的代码,它们与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)
}
}
我希望我能帮上忙。