- UIViewController画面からSwiftUI画面に遷移する方法
- SwiftUI画面からUIViewController画面に遷移する方法
を解説していきます。
良くある内容なのですが、細かい所で足りない記事しか無いので、この記事だけで完結するよう丁寧に解説していきます。
UIViewController画面からSwiftUI画面に遷移する
SwiftUIは、UIViewControllerのサブクラスであるUIHostingControllerでラッピングすれば、UIViewControllerとして扱うことができます。
import SwiftUI
....
let viewController = UIHostingController(rootView: SwiftUIView())
self.navigationController?.pushViewController(viewController, animated: true)
SwiftUI画面からUIViewController画面に戻る場合
pushViewControllerで遷移していても、presentViewControllerで表示されていても、次の方法で前の画面に戻ることができます。
/*
* SwiftUI画面
*/
// 変数の定義
@Environment(\.presentationMode) var presentationMode
....
// 前の画面に戻る処理
presentationMode.wrappedValue.dismiss()
Objective-CのUIViewControllerからSwiftUIを表示する場合
Objective-CではSwiftUIを扱うことができないため、一旦Swiftのクラス内でUIHostingContrllerによるラッピングを行い、生成されたUIViewControllerをObjective-Cで扱う、という一段階挟んだ形となります。
// Swiftのクラス
import SwiftUI
...
class SwiftClass {
static func createUIViewController() -> UIViewController {
return UIHostingController(rootView: SwiftUIView())
}
}
// Objective-Cのクラスで
UIViewController* uiViewController = [SwiftClass createUIViewController];
[self.navigationController pushViewController:uiViewController animated:YES];
SwiftUI画面からUIViewController画面に遷移する
UIViewControllerはUIViewControllerRepresentableでラッピングすれば、SwiftUIとして扱うことができます。
import SwiftUI
// UIViewControllerをラッピングする構造体の定義
struct UIKitView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let uiKitViewController: UIKitViewController = UIKitViewController()
return uiKitViewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
// SwiftUIView本体
struct SwiftUIView: View {
@State var showUIKitView = false
var body: some View {
NavigationView {
NavigationLink(destination: UIKitView(), isActive: $showUIKitView) {
EmptyView()
}
Button (action:{
showUIKitView = true
})
{
Text("UIViewController画面に遷移")
}
}
}
}
UIViewControllerをStoryboardで作っている場合
UIViewController内であれば、self.storyboard.instantiateViewController、でStoryboardで作ったUIViewControllerをインスタンス化できますが、SwiftUI内では同じように書けません。
そこで、Storyboard自体をまず作ってからインスタンス化します。
// UIViewControllerをラッピングした構造体の定義
struct UIKitView: UIViewControllerRepresentable {
...
func makeUIViewController(context: Context) -> UIViewController {
// Storyboardを生成する
let storyboard: UIStoryboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
// storyboardからインスタンス化する
let uiKitView = storyboard.instantiateViewController(withIdentifier: "uiKitView") as! UIKitView
....
}
...
}
UIViewController画面からSwiftUI画面に戻る場合
UIViewController内でpopViewControllerやdismissを呼んでも、SwiftUIとUIKitでは画面遷移の仕組みが違うので、SwiftUI画面には戻れません。
SwiftUI画面でNavigationLinkに指定した「showUIKitView」というフラグをUIViewController内でfalseにすることでSwiftUI画面に戻ることができるので、falseにするための仕組みを追加します。
class UIKitViewController: UIViewController {
....
// 画面を閉じる時に実行するdelegateを追加
var onDismiss: (() -> Void)?
....
// 画面を閉じる処理内でdelegateを呼び出す
@objc func close() {
onDismiss?() // 追加
}
}
// UIViewControllerをラッピングする構造体の定義
struct UIKitView: UIViewControllerRepresentable {
// バインドを追加
@Binding var showUIKitView: Bool
func makeUIViewController(context: Context) -> UIViewController {
let uiKitViewController: UIKitViewController = UIKitViewController()
// フラグをfalseにする処理を追加
uiKitViewController.onDismiss = {
self.showUIKitView = false
}
return uiKitViewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
// SwiftUIView本体
struct SwiftUIView: View {
@State var showUIKitView = false
var body: some View {
NavigationView {
// showUIKitViewフラグをBindする
NavigationLink(destination: UIKitView(showUIKitView: showUIKitView), isActive: $showUIKitView) {
EmptyView()
}
....
}
}
以上で、SwiftUI、UIViewControllerいずれからも遷移 & 戻ることができるようになります。