前書き
最近私が開発しているプロジェクトでもようやく新規画面や一部既存画面でも
SwiftUIを用いた開発を行うとようになりました!
ただ漠然とUIHostingControllerというものを用いてUIKitとSwiftUIを共存させていたのですが
そもそもUIHostingControllerって何してるの?と思ったので調べてみました!
1. UIHostingControllerとは何か
結論から書くとUIHostingControllerは、SwiftUIのViewをUIKitが扱えるUIViewControllerとしてラップするためのクラスです。
SwiftUIのViewは単体ではUIKitのビュー階層に入れられません。UIKitはUIViewControllerやUIViewを期待しているからです。
// これはできない
let swiftUIView = MySwiftUIView()
navigationController?.pushViewController(swiftUIView, animated: true) // :x: エラー
UIHostingControllerでラップすることで、SwiftUIのViewをUIKitの画面遷移に乗せられます:
// これならOK
let hostingController = UIHostingController(rootView: MySwiftUIView())
navigationController?.pushViewController(hostingController, animated: true) // :white_check_mark:
2.なぜ必要なのか
SwiftUIは画面遷移やライフサイクル管理を担わない設計のため、既存のUIKitベースのナビゲーション構造と共存させる必要があるからです。
実務では:
-
既存のナビゲーション構造(UINavigationController、UITabBarControllerなど)はそのまま使いたい
-
新機能や画面単位でSwiftUIを導入したい
-
既存のアーキテクチャ(Coordinator、RxFlowなど)を維持したい
UIHostingControllerがあることで、画面単位で段階的にSwiftUI化ができます。
// 既存のUIKit画面遷移の中に、SwiftUIの画面を1つだけ追加
func navigateToNewFeature() {
let swiftUIView = NewFeatureView()
let hostingController = UIHostingController(rootView: swiftUIView)
navigationController?.pushViewController(hostingController, animated: true)
}
全部書き換えるのではなく、新しい画面から少しずつSwiftUIに移行していける。これがUIHostingControllerの存在意義です。
3. ライフサイクルはどうなる?
両方動きます。
UIHostingControllerはUIViewControllerのサブクラスなので、UIKitのライフサイクルメソッドが呼ばれます。同時に、中に入っているSwiftUI Viewのライフサイクル(onAppear / onDisappear)も動きます。
実際に確認してみましょう。
// SwiftUI View
struct SampleView: View {
var body: some View {
Text("Hello")
.onAppear { print("🍎 SwiftUI: onAppear") }
.onDisappear { print("🍎 SwiftUI: onDisappear") }
}
}
// UIHostingController をサブクラス化
class SampleHostingController: UIHostingController<SampleView> {
override func viewDidLoad() {
super.viewDidLoad()
print("🍎 UIKit: viewDidLoad")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("🍎 UIKit: viewWillAppear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("🍎 UIKit: viewDidAppear")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("🍎 UIKit: viewWillDisappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("🍎 UIKit: viewDidDisappear")
}
}
実行結果(画面表示時):
🍎 UIKit: viewDidLoad
🍎 UIKit: viewWillAppear
🍎 SwiftUI: onAppear
🍎 UIKit: viewDidAppear
実行結果(画面非表示時):
🍎 UIKit: viewWillDisappear
🍎 SwiftUI: onDisappear
🍎 UIKit: viewDidDisappear
UIKitとSwiftUI、両方のライフサイクルがちゃんと動いていることが確認できました。
注意点
非表示時のonDisappearの順序は実行タイミングによって前後することがあります(viewWillDisappearの後だった
りviewDidDisappearの後だったり)。
厳密な順序に依存した実装は避けた方が安全です。
後書き
以上がUIHostingControllerとは何をしているのかなぜ必要なのかを記載させて頂きました、
まだまだUIKitを使用しているプロジェクト、これからSwiftUIに少しづつ切り替えていく予定のプロジェクトの方に参考になれば幸いです