(再確認)遅ればせながら、UIKitベースプロジェクトでSwiftUIの画面を表示してみた

## 概要
本当に今更ながら、UIKitベースのプロジェクトでSwiftUIの画面を表示してみました！
以下のようなサンプルを使用して、動作を確認しました！

[![Image from Gyazo](https://i.gyazo.com/5d16ad4c43d86722c71de216f0953eec.gif)](https://gyazo.com/5d16ad4c43d86722c71de216f0953eec)

:::note
この記事はこんな方におすすめ
- そろそろUIKitベースのプロジェクトにSwiftUIの画面を導入してみたい方
- UIKitとSwitfUIの共存に興味がある方
- iOSエンジニア　(全員やん。)
:::

:::note warn
警告
- 目新しい技術というよりは、技術の再確認というような記事です。
- 簡単な画面遷移のみできるようにしたサンプルなので、業務等で使用する際はより深い理解が必要になります。
:::

## モチベーション
なぜ今回この記事を書いたかというと、業務ではSwiftUIベースのプロジェクトアプリを実装しているのですが、仮に他チームのUIKitベースのプロジェクトにSwiftUIの画面を導入したらどうなるのかを自分で実装してみて確認したかったからです！！また、勉強会での共有も兼ねています。  
正直、新しい技術でもないので特に目新しい情報はありませんが、もし同じように『そろそろ新しい画面や機能は、UIKitではなくSwiftUIにするか！』と考えている方の一助となれば幸いです！！

## 前提知識
まずViewController→Swiftの遷移を実現する際に、前提知識として[UIHostingController](https://developer.apple.com/documentation/swiftui/uihostingcontroller)の理解が必要になります。
UIHostingControllerとは、平たく言えばSwiftUIで作成した画面をViewControllerから呼べるようにするApple純正のUIAPIです。
このUIHostingControllerを呼び元のViewControllerで初期化し、初期化の際にSwiftUIの遷移先を引数として渡してあげることでViewController→Swiftの遷移を実現できます！

他にもUIKitとSwiftUIのプロジェクト内での統合については、リフェレンスの[UIKit integration](https://developer.apple.com/documentation/swiftui/uikit-integration)に詳細が記載されているので、そちらを参照されてみてはいかがでしょうか。  
上記のリファレンスでは、SwiftUIベースのプロジェクト内でUIKitを使用するときのことについても言及されているので、結構重宝されていると思います！

## 実装例
ここからは、実際に動くコードを使用して解説していきます！工程としては、大きく3つになります！
なお、ここからはViewControllerを遷移元、SwiftUIを遷移先と呼びます。

##### 【工程1】 遷移作のSwiftUIの画面を作成する


```SwiftUIView.Swift
import SwiftUI

struct SwiftUIView: View {
    var body: some View {
        VStack {
            Text("I'm SwiftUI Based UI\nBut Wrapped HostingViewController...")
        }
    }
}
```

###### ポイント
呼び出されるSwiftUIのViewでは目新しいことは正直何もないような気がします！
しかし、一つ考えられるのは、@Bindingのようなプロパティはどのように使用すればいいのかを正しく理解しておくべきかと思いました！

###### 【工程２】 HostingViewControllerを作成してSwiftUIをラップしてViewControllerから呼び出せる準備をする

```HostingController.Swift

import SwiftUI

final class HostingController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setSwiftUIViewForHostingViewController()
    }
}

// MARK: - Private Extension

private extension HostingController {

    func setSwiftUIViewForHostingViewController() {
        let vc: UIHostingController = UIHostingController(rootView: SwiftUIView())
        view.addSubview(vc.view)
        vc.view.translatesAutoresizingMaskIntoConstraints = false
        vc.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
        vc.view.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 16).isActive = true
        vc.view.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -16).isActive = true
        vc.view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
    }
}

```

###### ポイント
大きく2つあります。
- 画面が表示れる時に、SwiftUIで作成したViewをUIHostingController初期化時に引数としてrootViewに渡してあげる点
ここが最大のミソなような気がしますが、SwiftUIの画面を`present`や`pushViewController`のような形では直接呼び出すことはできないため、一旦UIHostingViewControllerでラッピングしてあげます。そしてそのUIHostingViewControllerに遷移するような処理を親のViewControllerで呼んであげると画面遷移できるようになります。
少し面倒だなと感じますが、これだけでSwiftUIの画面をViewControllerでも呼べるのはなかなか便利ですかね！

- SwiftUIの画面を表示したいサイズで指定してあげる点
上記のコードでは、`setSwiftUIViewForHostingViewController()`がこれに該当するのですが、何も大きさを指定しなければ、画面は何も表示できず真っ暗な画面に遷移することになります。なぜこうなるかはわかりませんが、SwiftUIのViewを表示する際は、UIHostingViewController内で、大きさ指定してあげるのを忘れないようにしましょう！

##### 【工程3】親ViewであるViewControllerから、工程1で作成したHostingViewControllerを呼び出す

```ViewController.Swift

import UIKit

class ViewController: UIViewController {

    @IBAction func tappedPushToSwiftUIViewButton(_ sender: Any) {
        pushToSwiftUIView()
    }
    
    @IBAction func tappedPresentSwiftUIViewButton(_ sender: Any) {
        presntSwiftUIView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}

// MARK: - Private Extension

private extension ViewController {

    func pushToSwiftUIView() {
        let hostingVC = HostingController()
        navigationController?.pushViewController(hostingVC, animated: true)
    }

    func presntSwiftUIView() {
        let hostingVC = HostingController()
        hostingVC.modalTransitionStyle = .coverVertical
        hostingVC.modalPresentationStyle = .automatic
        present(hostingVC, animated: true)
    }
}

```

**Main.storyboard** (雑な実装なのは許してください...)
![スクリーンショット 2024-01-22 18.51.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630898/be87e12c-de3a-54a6-5a15-ec15bdd69ca4.png)

###### ポイント
最後の親ビューではViewControllerの画面遷移で行っていることと同じなので、ここら辺では考慮する点はそう大きくはなさそうです。
強いていうなら、`delegate`や監視する必要のある値等の扱いは考えないといけません。


## さいごに
本当に今更ながら、他の記事を参考にさせていただきながらUIKitベースプロジェクトでSwiftUIの画面を表示してみましたが、簡単な画面遷移のみの実装という点もあって案外不都合はなかったような気がしました。

また、業務ではSwiftUIベースのプロジェクトで痒いところに手が届かない場合に、UIKitで実装した画面を使用しているのでどちらの実装も経験できてよかったです！！  

実際にUIKitでゴリゴリに書いているプロジェクトで『新しい画面はSwiftUIで作成しよう！』と言うことになってくるとより複雑なプロジェクトになれば、かなりしっかり設計した上での実装が必要になるのは間違いなさそうです。  

僕自身は、ここ半年以上業務でUIKitの実装は全く触っていないので、久しぶりに触れていい機会にもなったのでUIKitベースの個人開発アプリも再度実装しようと思ったりやっぱり思わなかったりして、今日も1日が過ぎていきました...

知識が増え次第、追記もしていきます！

## 参考資料
https://developer.apple.com/documentation/swiftui/uikit-integration


https://developer.apple.com/documentation/swiftui/uihostingcontroller


https://developer.apple.com/documentation/swiftui/uihostingcontrollersizingoptions


https://developer.apple.com/documentation/swiftui/uiviewrepresentable


https://zenn.dev/oimo_revolution/articles/70c4e714b778b8


https://qiita.com/kamimi01/items/bbef57d845034b5c5b1b