iOS

UIViewControllerからSwiftUIを表示 & SwiftUIからUIViewControllerを表示する方法を丁寧に解説

  • 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いずれからも遷移 & 戻ることができるようになります。