はじめに
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についてはだいたい一緒なので省略。
説明
- 利用したいViewControllerをtypealiasで指定します。ただし、ここで書かずにメソッド
makeUIViewController(context:)
とupdateUIViewController(context:)
で明示すればいいだけです。私はtypealias使うほうが(設計開発時には試行錯誤する間は)楽だと感じてます- このサンプルでは
UINavigationController
にしていますがそれは自由です
- このサンプルでは
-
makeUIViewController(context:)
はViewControllerを最初に作成する1回きり呼び出されます。たいてい表示される初回のみのはずです- Swiftはファクトリメソッドを
make~
ではじめよ、というスタイルをガイドで示しておりそれに従った命名となっています - 引数のcontextからは後述するCoordinatorのインスタンスが取り出せます
- https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)
- Swiftはファクトリメソッドを
-
updateUIViewController(_:context:)
はSwiftUIから更新が必要になった場合に呼び出されます。 - 必須では有りませんが、後述するCoordinatorを利用する場合にそれを作成するメソッドであり、
makeUIViewController(context:)
実行前に1度のみ呼び出されます - 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のプロパティにアクセスしている(できてしまうとも言うが...)
- makeCordinatorする際に引数でselfを渡す
- SwiftUI.Viewにも準拠してるんだけど名前は~ViewControllerでいい
- やってること
慣れないうちは(もしくは慣れても)ややこしいと感じる部分かもしれませんが
- 自作するのは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とするのはいいですね。