UIKitを部分的にSwiftUIに置き換えていく方法について

## はじめに
SwiftUIのAPIが年々充実していく中で、`UIKit`から　`SwiftUI`への切り替えを考えられている方も多いのではないでしょうか？
そんな中で、今年のWWDC2022にて、[Use SwiftUI with UIKit](https://developer.apple.com/videos/play/wwdc2022/10072/)という、UIKitからSwiftUIへの置き換えについて語られているセッションがありました。
今回は、そのセッションの内容を元に、`UIKit`から　`SwiftUI`への置き換え方法を見ていきたいと思います。

## UIHostingControllerを使う
iOS13から既に提供されている方法です。
`SwiftUI`の`View`を`UIViewController`に格納することで`SwiftUI`を`UIKit`に取り込んで表示します。

```swift
/// SwiftUI View
let rootView = RootView()
let hostingController = UIHostingController(rootView: rootView)

present(hostingController, animated: true)
```

`UIHostingController`を使用する場合、`UIKit`と`SwiftUI`間でのデータの受け渡し方法についてはいくつかやり方があリます。

まず一つ目は、データ変更時は手動で`UIHostingController`の`rootView`を更新する方法です。
シンプルではありますが、毎回手動で`rootView`の更新作業を行う必要があります。

```swift
/// SwiftUI
struct RootView: View {
    var text: String

    var body: some View {
        Text(text)
    }
}

/// UIKit
final class HostingController: UIViewController {
    let hostingController = UIHostingController<RootView>
    var text: String {
        // テキストの更新が入ったら、手動でRootViewの更新処理を走らせる
        didSet { update() }
    }

    func update() {
        hostingController.rootView = RooteView(text: text)
    }
}
```

2つ目に、`@ObservedObject`を使う方法です。
`UIKit`側から`SwiftUI`側に`@ObservedObject`を渡しておくことで、`UIKit`側で`@ObservedObject`が更新されたらそれを`SwiftUI`側でで検知し更新が走ります。

```swift
/// ObservableObject
final class DataSource: ObservableObject {
    @Published var text: String = ""
}

/// SwiftUI
struct RootView: View {
    @ObservedObject var dataSource: DataSource
    var body: some View {
        Text(dataSource.text)
    }
}

/// UIKit
final class HostingController: UIViewController {
    let dataSource = DataSource()
    let hostingController = UIHostingController<RootView>

    override func viewDidLoad() {
        super.viewDidLoad()

        let rootView = RootView(dataSource: dataSource)
        hostingController = UIHostingController(rootView: rootView)
    }
}
```
基本的にはデータの受け渡しに関しては`@ObservedObject`を使う形を採用するのが良いかと思います。

## UIHostingConfigurationを使う
`UIHostingConfiguration`はiOS16からの新機能です。
既存の　`UITableView`または`UICollectionView`の中で`SwiftUI`を利用する仕組みで、余分な`View`や`ViewController`を介さずに`SwiftUI`を使ったカスタムセルを表示することができます。
使い方は簡単で、`UITableViewCell`または`UICollectionViewCell`の`contentConfiguration`に対し、`UIHostingConfiguration`の`content`引数で`SwiftUI`のコードを入れて渡すだけです。
```swift
let cell: UITableViewCell

// SwiftUIのコードを直接書き込む
cell.contentConfiguration = UIHostingConfiguration(content: {
    Label("text", systemImage: "iamge_name")
        .font(.system(size: 12))           
})
.background(.white) // バックグラウンドのスタイルを設定
.margins(.horizontal, 16) // マージンを設定

// SwiftUI Viewを別途作成してそれを入れ込むことも可能
cell.contentConfiguration = UIHostingConfiguration(content: {
    LabelView()
        .swipeActions(content: { ... }) // Cellのスワイプ時のアクションを設定
})

struct LabelView: View {
    var body: some View {
        Label("text", systemImage: "iamge_name")
            .font(.system(size: 12))  
    }
}
```

また、セルのステータスに応じて処理を変えたい場合は`configurationUpdateHandler`を使用することで、セルの状態変更にも柔軟に対応できます。

```swift
// セルの状態変更が起こるタイミングで都度呼ばれ、新規でUIHostingConfigurationを作成してcellに適用する
cell.configurationUpdateHandler = { cell, state in
    HStack {
        Label("text", systemImage: "iamge_name")
        Spacer()
        // セルが選択状態の時のみチェックマークを表示するようにする
        if state.isSelected {
            Image(systemName: "checkmark")
        }
    }
})
```

## おわりに
iOS16から新たに登場した`UIHostingConfiguration`のおかげで、より簡単に、そしてより部分的にSwiftUIを部分的に導入していくことが可能になったかなと思います！
現状、iOS16以降でのみしか使用できないため、直近で使うことは難しいかもしれませんが、今後`SwiftUI`に置き換えていく際には大活躍してくれそうな気がしております！

また、何かご意見等ありましたら、コメントいただけますと幸いです。
どうぞよろしくお願いいたします。

## 参考
[Use SwiftUI with UIKit - WWDC22 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2022/10072/)