SwiftUIとUIKitを組み合わせた場合の画面遷移について

こんにちは。

たまにiOS開発を行なっており、UIKit や Storyboard によるUI開発が大変に感じており、影響のない範囲で SwiftUI による実装を組み合わせていました。

その際に、SwiftUI で作成した View とUIKit(UIViewController/Storyboard)で作成した View の画面遷移について、個人的にわかりづらいと感じたので備忘録としてまとめます。

注意:個人的には、依然として SwiftUI 制限があるため、可能な限り混在させないというのは大事かなと思っています。
(ただ、SwiftUI による実装のしやすさを考えると使いたくなる...)

UIKitのViewからSwiftUIのViewに遷移する

この場合は、UIHostingController を利用します。

A UIKit view controller that manages a SwiftUI view hierarchy.

UIHostingController より

引数 rootView に、SwiftUI で作成した View を指定します。
UIHostingVontroller で得た View に対して、modalPresentationStyle の指定や UIKit で使われる present() を用いて遷移することができました。

let view = UIHostingController(rootView: ContentView())
view.modalPresentationStyle = .fullScreen
self.present(view, animated: true)

SwiftUIのViewからUIKitのViewに遷移する

この場合、UIHostingController のように UIViewController/Storyboard で実装した View を呼び出すことのできる関数は見つかりませんでした。

調べてみると、SwiftUI で作成した View 上に UIViewController のオブジェクトを作成できる UIViewControllerRepresentable と呼ばれるプロトコルがあったので、こちらを用いて UIKit で作成した View を呼び出します。

Use a UIViewControllerRepresentable instance to create and manage a UIViewController object in your SwiftUI interface.

UIViewControllerRepresentable Overview より

まず、SwiftUI 上で画面遷移の部分を用意します。今回は、ボタンを押したときに遷移するようにします。

@State private var isPresented: Bool = false
    
var body: some View {
    Button(action: {
        isPresented = true
    }) {
        Text("Return")
    }
    .fullScreenCover(isPresented: $isPresented) {
        // ここで、UIViewControllerRepresentableによるインスタンスを呼び出す //
    }
}

次に、UIViewControllerRepresentable インスタンスを作成します。このインスタンスは、作成したときに1度だけ呼び出される makeUIViewController() と、View を更新した場合に呼び出す updateUIViewController() で構成されています。

今回は、ボタンが押されたときにインスタンスを作成し、画面遷移するだけなので、makeUIViewController() にて実装します。Return Value の ViewController は、UIKit で作成した遷移先の UIViewController です。あとは、UIKit と同じように、Storyboard/UIViewController による画面遷移です。

struct ViewControllerRepresentable: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> ViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "vc") as! ViewController
        vc.modalPresentationStyle = .overFullScreen
        return vc
    }

    func updateUIViewController(_ uiViewController: ViewController, context: Context) {
    }
}

このインスタンスを、先ほどの .fullScreenCover にて作成します。

@State private var isPresented: Bool = false
    
var body: some View {
    Button(action: {
        isPresented = true
    }) {
        Text("Return")
    }
    .fullScreenCover(isPresented: $isPresented) {
        ViewControllerRepresentable()
    }
}

これで SwiftUI で作成した View から UIKit で作成した View に遷移できるようになりました。

複雑にみえますが、そもそも UIViewControllerRepresentable は UIKit の View や機能を SwiftUI の View に入れたい場合に利用されるラッパーのようなものなので、使い方としては順当かもしれません。

まとめ

最初にも書いたように SwiftUI に制限があるため、このようにお互いをラッパーするような形で使うことになると思います。ですが、SwiftUI で実装することでやりやすいこともあるため、参考になればと思いまとめました。
このあとの動向も見ていきたいと思います。

参考にさせていただきました。

ChatGPTに聞いてみました。

質問:SwiftUI で作成した View と UIKit で作成した View が共存する状況において、SwiftUI で作成した View からすでに作成済みである UIKit で作成した View に画面遷移する方法をコードで教えてください。

ChatGPT にて、上記のように聞いてみたところ結果も得られました。

何か参考にしたリファレンスがあるか聞きました。

Apple 公式のドキュメントをリファレンスしているようです。

改めて凄さを感じました。。。

この記事が気に入ったらサポートをしてみませんか?