UIDocumentPickerViewController在Mac Catalyst上不显示任何内容

时间:2019-11-10 19:44:17

标签: swiftui maccatalyst

UIDocumentPickerViewController在iOS上有效,但在Mac Catalyst上无效。是否有其他替代方法可以解决此问题?顺便说一句,Mac Catalyst上没有NSOpenPanel。

enter image description here

3 个答案:

答案 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)

screenshot 以下示例适用于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)
        }
    }

我希望我能帮上忙。