3

UIDocumentPickerViewController 适用于 iOS,但不适用于 Mac Catalyst。有没有其他方法可以解决这个问题?顺便说一句,NSOpenPanel 在 Mac Catalyst 上不可用。

在此处输入图像描述

4

4 回答 4

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)
    }

}
于 2020-06-05T23:29:43.397 回答
2

截屏 以下示例适用于 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,没有他的帮助,我无法解决这个问题。

于 2019-11-13T13:06:00.110 回答
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)
        }
    }

我希望我是有帮助的。

于 2020-02-15T11:48:02.407 回答
0

您必须转到 Target -> Signing & Capabilities 并设置文件访问权限。

于 2021-11-24T11:30:15.657 回答