CatalystとSwiftUIを同時に使うとUIDocumentPickerViewControllerやUIActivityViewControllerが画面に表示されない

投稿者: nantekottai 投稿日:

Pocket

CatalystでiOS / macOS両方で動くアプリを作ってみようと思ったところ、表題の通りmacOSでUIDocumentPickerViewController / UIActivityViewControllerが正しく動作しない(画面に表示されない)問題にぶつかり、ハマってしまった。

11/3追記: ワークアラウンドを見つけたので追記

CatalystではNSOpenPanel / NSSavePanelを使用できない

macOSアプリでファイルを開いたり保存先を指定したりする際にはNSOpenPanel / NSSavePanelを使用するが、Catalystではこれらのクラスを使用することができない。Catalystを使ったmacOSアプリでは、代わりにUIDocumentPickerViewControllerを使用するとNSOpenPanel / NSSavePanel同様のモーダル/シートを表示させることができる。

SwiftUIを使わずにUIDocumentPickerViewControllerをpresentして使う場合は問題なく動作する

このUIDocumentPickerViewControllerは、SwiftUIを使わないCatalystアプリでは問題なく動作するのだが、SwiftUIの.sheet(isPresented:)を使って表示しようとすると空白のモーダルが表示されてしまう。

ちなみに、UIImagePickerControllerの場合は下記のように問題なく表示される。

UIActivityViewControllerに関してはUIDocumentPickerViewController同様の表示となってしまう。UIImagePickerControllerでは問題が起きず、UIDocumentPickerViewControllerとUIActivityViewControllerの二つで問題が発生することを考えると、ファイルアクセスのパーミッション関連の問題の可能性が考えられる。ただし後述のワークアラウンドを使うと正しく表示できることから、パーミッション関連の問題の可能性は低そうだ。

純粋なmacOSアプリのSwiftUI + NSOpenPanel / NSSavePanelの組み合わせも問題なく動作する

原因特定のため、試しにCatalystを使わない純粋なmacOSアプリでSwiftUIを有効にし、NSOpenPanel / NSSavePanelを表示するコードを書いてみたところ、こちらは問題なく動作した。問題はSwiftUIとCatalystを組み合わせた場合のみ発生するようだ。Catalyst + SwiftUIで使う場合に追加の設定が必要な可能性も考えられるが、不具合である可能性もありそうだ。

ViewControllerを一つ間に挟んでPresentするとうまくいく

苦肉の策で、UIViewControllerRepresentableなViewを用意し、そこからpresentでUIDocumentPickerViewControllerを表示させるコードを書いてみたところ、正しく表示させることに成功した。

// Delegateに関する部分のコードは省略

class MyDocumentPickerViewController: UIViewController {
    func setupViews() {
        let button = UIButton()
        button.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
        button.setTitle("Open", for: .normal)
        button.addTarget(self, action: #selector(onButtonClick(sender:)), for: .touchUpInside)
        view.addSubview(button)
    }
    
    @objc func onButtonClick(sender: Any?) {
        let picker = UIDocumentPickerViewController(documentTypes: [kUTTypeImage as String], in: .import)
        picker.modalPresentationStyle = .fullScreen
        present(picker, animated: true, completion: nil)
    }
}

本来であれば不要な実装であるが、取り急ぎCatalyst + SwiftUIアプリでUIDocumentPickerViewControllerを使いたい場合は上記のようなワークアラウンドを行うのが良さそう。

その他の対策

上述の実装はSwiftUIをベースに、その中でViewControllerを使用する方法を試したが、逆にStoryboardベースでUIを作りUIHostingControllerを使って一部の画面だけSwiftUIを使用する、という方法でも大丈夫そうだ。

SwiftUI / Catalystについては、まだ登場から時間が短く情報が少ない。組み合わせて使用する場合に関する情報はさらに少ないため、このような問題にぶつかってしまった場合の対応が結構大変だ。リリースの遅延が許されないようなアプリやセキュアな作りが求められるアプリでは、この組み合わせはまだ避けておいた方が良いかもしれない。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です