UIKitのUIViewController/UIViewをSwiftUIで利用する場合の利用方法とその詳細


はじめに

UIKitのUIViewController/UIViewをSwiftUIで使う場合の方法とその詳細について書いておきます。

大抵コード書いてるときに検索してから要点が見つかるまで時間がかかるため先に結論をテンプレートとして示します。

結論: このテンプレートを埋める

UIViewControllerを利用したい場合は、下記コードを加筆修正しいけば最低限動くレベルになるはずです。

import SwiftUI
import UIKit

struct SampleViewController: UIViewControllerRepresentable {
    // 1. 利用したいViewControllerにエイリアスをつける(もちろんつけずに下記コードで都度書いてもいい)
    typealias UIViewControllerType = UINavigationController

    // 2. 必須のメソッド。作成したいViewControllerを返すメソッドを実装する
    func makeUIViewController(context: Context) -> UIViewControllerType {

    }

    // 3. 必須のメソッド。Viewが更新された場合に必要な処理を実装する
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {

    }

    // 4. Coordinatorのファクトリーメソッドを実装する。
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    // 5. Coordinatorを定義する
    class Coordinator {

    }
}

UIViewについてはだいたい一緒なので省略。

説明

  1. 利用したいViewControllerをtypealiasで指定します。ただし、ここで書かずにメソッドmakeUIViewController(context:)updateUIViewController(context:)で明示すればいいだけです。私はtypealias使うほうが(設計開発時には試行錯誤する間は)楽だと感じてます
    • このサンプルではUINavigationControllerにしていますがそれは自由です
  2. makeUIViewController(context:)はViewControllerを最初に作成する1回きり呼び出されます。たいてい表示される初回のみのはずです
  3. updateUIViewController(_:context:)はSwiftUIから更新が必要になった場合に呼び出されます。
  4. 必須では有りませんが、後述するCoordinatorを利用する場合にそれを作成するメソッドであり、makeUIViewController(context:)実行前に1度のみ呼び出されます
  5. Coordinatorを定義します。Coordinatorは利用するViewControllerがイベントを処理するために、そのイベントハンドリングを行う型を定義することができます。

Appleのチュートリアル

AppleはSwiftUIによるアプリ開発チュートリアルを公開していて、UIViewControllerRepresentableについても利用例が示されています。
https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

チュートリアルの完成コードを図で説明すると次のような感じ

チュートリアルでは左右のスワイプジェスチャーでページングする機能を持つ、UIPageViewControllerを利用し、独自のPageViewControllerをSwiftUI.Viewとしています。

  • struct PageViewController: UIViewControllerRepresentable
    • やってること
      • UIViewControllerRepresentableに準拠させて作る
      • currentPageを@Bindingとして変化を外部に伝える
    • 気になるポイント
      • SwiftUI.Viewにも準拠してるんだけど名前は~ViewControllerでいい
        • ~Viewとかにしてもと思ういいけど
      • CoordinatorにはparentとしてPageViewControllerを保持する
        • makeCordinatorする際に引数でselfを渡す
          • Coordinator側からPageViewControllerのプロパティにアクセスしている(できてしまうとも言うが...)

慣れないうちは(もしくは慣れても)ややこしいと感じる部分かもしれませんが

  • 自作するのはPageViewController
    • SwiftUI.Viewプロトコルに準拠している
  • 利用するのはUIPageViewController
    • UIKitのUIPageViewControllerの表示とデリゲートを利用している

どういうときにUIViewControllerRepresentableとUIViewRepresentableを使い分けるか

Appleのチュートリアルから察するに、利用するUIKitのコンポーネントがUIViewControllerならUIViewControllerRepresentable、UIViewならUIViewRepresentableにすればいいんじゃないかと思います。

その他

TCAだと

このチュートリアルでは@Bindingを使って結果を共有しています。これがTCAだったらstruct PageViewController: UIViewControllerRepresentableにStoreをもたせてmake〜でviewStateに対して処理を行ってもかまわないし、そうではなく自作するViewController内にStoreをバケツリレーするだけでもかまわないでしょうね。

Context

makeUIViewController(context:)から取り出せるContextはtypealias Context = UIViewControllerRepresentableContext<Self>です。

UIViewControllerRepresentableContextはCoordinator以外にSwiftUI.EnvironmentValuesも取り出すことができます。

こういうながったらしい型をtypealiasでContextとするのはいいですね。