UIDocumentPickerViewController works on iOS but not on Mac Catalyst. Is there any alternatives to workaround this issue? BTW, NSOpenPanel is unavailable on Mac Catalyst.

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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With