NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

【UIKit】UIHostingControllerを使ってUIViewControllerの中でSwiftUIのViewを表示する

概要

SwiftUIのViewでUIViewControllerやUIViewを呼び出す方法はUIViewControllerRepresentableやUIViewRepresentableを使用することで可能です。 逆にUIViewControllerの中でSwiftUIのViewを表示するにはUIHostingControllerを使用します。
今回はUIHostingControllerを使ってUIViewControllerの中でSwiftUIのViewを表示する方法を紹介します。

tech.nri-net.com

環境

この記事は以下のバージョン環境のもと作成されたものです。
【Xcode】14.0.1
【iOS】16.0.2
【macOS】Monterey バージョン 12.5

実装

実装方法はUIHostingController(rootView: MySwiftUIView())の形でrootViewに対してSwiftUIのViewを渡すことでUIViewController側で使用できるようになります。 例としては以下動作の様子とサンプルコードです。

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        addOpenViewButton()
    }

    lazy var openViewButton: UIButton = {
        let button:UIButton = UIButton()
        button.frame.size.width = 200
        button.frame.size.height = 100
        button.titleLabel?.font = .systemFont(ofSize: 20)
        button.setTitle("SwiftUI Viewを開く", for: UIControl.State.normal)
        button.setTitleColor(UIColor.systemBlue, for: .normal)
        button.addTarget(self, action: #selector(didTapOpenView(sender:)), for: .touchUpInside)
        return button
    }()

    private func addOpenViewButton() {
        openViewButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(openViewButton)
        NSLayoutConstraint.activate([
            openViewButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            openViewButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }

    @objc func didTapOpenView(sender _: UIButton) {
        let vc = UIHostingController(rootView: MySwiftUIView())
        vc.modalPresentationStyle = .fullScreen
        present(vc, animated: true)
    }
}
struct MySwiftUIView: View {
    @Environment(\.dismiss) private var dismiss
    @State var text = "open SwiftUI View"
    var body: some View {
        ZStack {
            Color.green
                .ignoresSafeArea()
            VStack {
                Text(text)
                    .foregroundColor(.white)
                Button {
                    dismiss()
                } label: {
                    Text("閉じる")
                }

            }
        }
    }
}

少し解説するとMyViewControllerの中でdidTapOpenViewを叩くことでUIHostingControllerでラップされたMySwiftUIViewを呼び出しています。 またdismissを叩くことで閉じています。

値を渡す

ここでは簡単に静的に渡したいケースを例として紹介させていただきます。 didTapOpenViewの中身を以下に変更します。

@objc func didTapOpenView(sender _: UIButton) {
    let text = "UIViewControllerから開いたよ"
    let vc = UIHostingController(rootView: MySwiftUIView2(text: text))
    vc.modalPresentationStyle = .fullScreen
    present(vc, animated: true)
}

これでMyViewControllerから開いた時はMySwiftUIView側で表示されている文字が変更されます。

まとめ

UIHostingControllerでラップするだけで使用できるので非常に簡単に実装できました!
またCombineを使用したUIKit側とSwiftUI側でのデータバインディングについても書きたいと思います。

執筆者岡優志

iOSエンジニア
iOSを専門とし、モバイルアプリの開発を行なっています。

Twitter