Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to popup a document picker in ios and macos using catalyst

I'm trying to popup a document picker using mac catalyst, but all I get is a blank screen.

All works well on ios 13.2.2 on iPad and iPhone, but not on macos 10.15.1 catalina.

Does anyone knows how to popup a document picker in both ios and macos using catalyst?

with my entitlements file having:

<key>com.apple.security.app-sandbox</key>
<false/>

Here is the test code that shows the problem.

import Foundation
import SwiftUI

struct ContentView: View {
@State var isFilePickerShown = false

var body: some View {
    VStack {
        Button(action: { self.isFilePickerShown.toggle() }) {
            Image(systemName: "rectangle.and.paperclip").resizable().frame(width: 70, height: 70)
        }
    }.sheet(isPresented: $isFilePickerShown, onDismiss: {self.isFilePickerShown = false}) {
        DocPickerViewController(callback: self.filePicked, onDismiss: { self.isFilePickerShown = false })
    }
}

func filePicked(_ url: URL) {
    print("\nThe url is: \(url)")
}

}

struct DocPickerViewController: UIViewControllerRepresentable {

private let docTypes: [String] = ["com.adobe.pdf", "public.text", "public.composite-content"]
var callback: (URL) -> ()
private let onDismiss: () -> Void

init(callback: @escaping (URL) -> (), onDismiss: @escaping () -> Void) {
    self.callback = callback
    self.onDismiss = onDismiss
}

func makeCoordinator() -> Coordinator { Coordinator(self) }

func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<DocPickerViewController>) {
}

func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
    let controller = UIDocumentPickerViewController(documentTypes: docTypes, in: .import)
    controller.allowsMultipleSelection = false
    controller.delegate = context.coordinator
    return controller
}

class Coordinator: NSObject, UIDocumentPickerDelegate {
    var parent: DocPickerViewController
    init(_ pickerController: DocPickerViewController) {
        self.parent = pickerController
    }
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        parent.callback(urls[0])
        parent.onDismiss()
    }
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        parent.onDismiss()
    }
}
}

I got something going with the following code. But really this is a messy business and I feel this is not the answer.

Now I have the same problem trying to display a UIActivityViewController. I can display it on Mac using the same approach but I can't control where it shows up. It's always at (0,0).

struct FilePicker: UIViewControllerRepresentable {

private let docTypes: [String] = ["com.adobe.pdf", "public.text", "public.composite-content"]
private let controller: FilePickerController?

var callback: (URL) -> ()

private let onDismiss: () -> Void

init(callback: @escaping (URL) -> (), onDismiss: @escaping () -> Void) {
    self.callback = callback
    self.onDismiss = onDismiss
    self.controller = FilePickerController(documentTypes: docTypes, mode: .import)
}

func makeCoordinator() -> Coordinator {
    return Coordinator(self)
}

func updateUIViewController(_ uiViewController: FilePickerController, context: UIViewControllerRepresentableContext<FilePicker>) { }

func makeUIViewController(context: Context) -> FilePickerController {
    return controller!
}

class Coordinator: NSObject, FilePickerControllerDelegate {
    var parent: FilePicker

    init(_ filePicker: FilePicker) {
        self.parent = filePicker
        super.init()
        self.parent.controller?.delegate = self
    }

    func documentPicker(_ controller: FilePickerController, didPickDocumentsAt urls: [URL]) {
        parent.callback(urls[0])
        parent.onDismiss()
    }

    func documentPickerWasCancelled(_ controller: FilePickerController) {
        parent.onDismiss()
    }
}
}


protocol FilePickerControllerDelegate: class {
func documentPickerWasCancelled(_ controller: FilePickerController)
func documentPicker(_ controller: FilePickerController, 
didPickDocumentsAt urls: [URL])
}

class FilePickerController: UIViewController, UIDocumentPickerDelegate {

weak var delegate: FilePickerControllerDelegate?

let viewController: UIDocumentPickerViewController?

public init(documentTypes: [String], mode: UIDocumentPickerMode) {
    viewController = UIDocumentPickerViewController(documentTypes: documentTypes, in: mode)
    super.init(nibName: nil, bundle: nil)
}

required public init?(coder: NSCoder) {
    viewController = UIDocumentPickerViewController(coder: coder)
    super.init(coder: coder)
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if let viewController = viewController {
        viewController.delegate = self
        self.present(viewController, animated: animated)
    }
}

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    self.dismiss(animated: false) {
        self.delegate?.documentPicker(self, didPickDocumentsAt: urls)
    }
}

func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
    self.dismiss(animated: false) {
        self.delegate?.documentPickerWasCancelled(self)
    }
}

}

I call it like this:

.sheet(isPresented: $isShown, onDismiss: {self.isShown = false}) {
        #if targetEnvironment(macCatalyst)
            FilePicker(callback: self.filePicked, onDismiss: { self.isShown = false })
        #else
            DocPickerViewController(callback: self.filePicked, onDismiss: { self.isShown = false })
        #endif
    }
like image 649
workingdog support Ukraine Avatar asked Nov 09 '19 03:11

workingdog support Ukraine


People also ask

What is iOS Catalyst?

By Tyler Lacoma February 14, 2020. Share. Project Catalyst is a big deal in Apple circles these days, and for good reason: It refers to Apple's project to merge development for iOS and Mac apps, allowing you to use them interchangeably on all kinds of Apple devices.

Whats Mac Catalyst?

Mac Catalyst is the technology that lets you bring your existing iOS applications to macOS, allowing them to take full advantage of the Mac's larger display, integrated keyboard, and mouse or trackpad.


3 Answers

For all with code from hstdt and workingdog.

import SwiftUI

struct ContentView: View {
    @State private var isFilePickerShown = false
    @State private var picker = DocumentPicker()

    var body: some View {
        VStack {
            Button(action: {
                self.isFilePickerShown.toggle()
                 #if targetEnvironment(macCatalyst)
                UIApplication.shared.windows[0].rootViewController!.present(self.picker.viewController, animated: true)
                #endif
            }) {
                Image(systemName: "rectangle.and.paperclip").resizable().frame(width: 70, height: 70)
            }
        }
        .sheet(isPresented: $isFilePickerShown, onDismiss: {self.isFilePickerShown = false}) {
            DocPickerViewController(callback: self.filePicked, onDismiss: { self.isFilePickerShown = false })
        }
    }

    func filePicked(_ url: URL) {
        print("\nThe url is: \(url)")
    }
}

and now create new swiftui file DocumentPicker for MacOS:

import SwiftUI

final class DocumentPicker: NSObject, UIViewControllerRepresentable {
    typealias UIViewControllerType = UIDocumentPickerViewController

    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]) {
        print(urls)
    }

    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        controller.dismiss(animated: true) {
        }
        print("cancelled")
    }
}

This code popup a document picker in both iOS and MacOS using catalyst. With MacOS Catalyst work with DocumentPicker Class and with iOS with DocPickerViewController write by workingdog (see workingdog post above)

like image 78
Cesare Piersigilli Avatar answered Oct 19 '22 04:10

Cesare Piersigilli


Workaround: present picker controller for catalyst

UIApplication.shared.firstKeyWindow?.rootViewController!.present(self.picker.controller!, animated: true)

extension UIApplication {
    public var firstKeyWindow: UIWindow? {
        windows.first(where: { $0.isKeyWindow })
    }
}
like image 26
hstdt Avatar answered Oct 19 '22 04:10

hstdt


That's the example from Cesare Piersigilli on GitHub: https://github.com/AndreasPrang/Catalyst-File-Picker

like image 43
Andreas Prang Avatar answered Oct 19 '22 04:10

Andreas Prang