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