SwiftUIでNibでデザインしたCellやViewを使う

SwiftUIがUIKitと併用できると聞いて、コード記述のUIクラスだけでなく、Nibもできそうだったので調べて記事を書いてみました！
サンプルコード: https://github.com/kntkymt/SwiftUIWithNib

※本記事のスクリーンショットは`MacOS Mojave 10.14.5`, `Xcode10.2`上で撮影されたものであり、 beta版のスクリーンショットではありません。
SwiftUIの使用にはこちら記事を参考にさせていただきました。
[macOSの複数バージョンを同一パーティションにインストール](https://qiita.com/bellx2/items/8e78fd682c1b966c4db6)
## 前提
このようなUITableViewCell, Nib(`.xib`)があったとします。

```swift
import UIKit

final class HogeTableCell: UITableViewCell {

    // MARK: - Outlet

    @IBOutlet private weak var image: UIImageView! 
    @IBOutlet private weak var categoryLabel: UILabel!
    @IBOutlet private weak var titleLabel: UILabel!
    @IBOutlet private weak var descriptionLabel: UILabel!
}
```

<img width="455" alt="スクリーンショット 2019-06-09 0.36.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388166/3648b46e-ecf9-609e-67a1-f75250a1a369.png">

## 本題

Nibを元に`HogeTableCell`クラスのインスタンスを生成し、返すビルダーメソッドを追加します。これをインスタンスを生成する側でinitの代わりに用います。

```swift
class func build() -> UIView {
    return UINib("HogeTableViewCell", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
```
SwiftUI上で使用する分には問題ないと考え、`UIView`にダウンキャストしています。

Nib使用時に特別なのは以上です。

あとは、[SwiftUItutorial Creating and Combining Views](https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)のSession5にもあるように、`UIViewRepresentable`でラップしてあげれば、SwiftUI上で通常通り利用できるようになります。`.xib`でいじった部分もちゃんと反映されてます。

```swift
import SwiftUI

struct SwiftUIHogeTableView: UIViewRepresentable {
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // サイズの調整など
    }
    
    func makeUIView(context: Context) -> UIView {
        // インスタンスの生成
        TopImageCollectionCell.build()
    }
}

#if DEBUG
struct SwiftUIHogeTableView_Previews : PreviewProvider {
    static var previews: some View {
        // ここでxibのViewのサイズとプレビューのサイズを同じにする
        SwiftUIHogeTableView()
            .frame(width: 355, height: 420)
            .previewLayout(.sizeThatFits)
    }
}
#endif
```


```swift

import SwiftUI

struct ContentView : View {
    var body: some View {
        SwiftUIHogeTableView()
            .frame(width: 355, height: 420) 
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif
```

beta版のスクリーンショットはNDAに引っかかるかが怪しいラインらしいので、念の為上げない事にします。すいません！
色々適当ですが、githubにサンプルコードを上げているので、よければそちらをご自分で確認してみてください！
ご指摘があればいただきたいです！

SwiftUI、簡単にiOSっぽいデザインが出来ていいですね。どうしてもSwiftUIでデザインが難しいところはNibを使ってできそうでよかったです。

## おまけ

Nibからクラスのインスタンスを生成する機構を抽象化してprotocol化するのがオススメです。
※クラス名と`.xib`名が同一であることを前提に抽象化を行っているので、違う名前にすると使えません。

```swift
import UIKit

protocol NibInstantiatable: class {
    associatedtype Instance
    static var NibName: String { get }
    static var nib: UINib { get }
    static func instantiateView() -> Instance
}

extension NibInstantiatable where Self: UIView {
    
    static var NibName: String {
        return String(describing: self)
    }
    
    static var nib: UINib {
        return UINib(nibName: NibName, bundle: nil)
    }
    
    static func instantiateView() -> Self {
        return nib.instantiate(withOwner: nil, options: nil)[0] as! Self
    }
}
```
クラス側では以下の様に記述できます。

```swift
import UIKit

final class HogeTableCell: UITableViewCell, NibInstantiatable {

    // MARK: - Outlet

    @IBOutlet private weak var image: UIImageView! 
    @IBOutlet private weak var categoryLabel: UILabel!
    @IBOutlet private weak var titleLabel: UILabel!
    @IBOutlet private weak var descriptionLabel: UILabel!

    // MARK: - Builder
    
    class func build() -> UIView {
        let view = instantiateView()
        return view
    }
}
```

`build()` の引数にModelを入れるようにすればOutletへのデータの注入もできそうですね。

```swift
class func build(model: HogeModel) -> UIView {
        let view = instantiateView()
        view.image = model.image
        view.categoryLabel.text = model.category
        view.titleLabel.text = model.title
        view.descriptionLabel = model.description

        return view
}
```