UITableViewにタグを並べて表示するようなセルを持たせる（画面回転対応版）

# 概要
UITableViewでUIを組んでいて、例えば検索キーワードなどをタグのUIにして並べるようなときに使えるテクニックを説明します。
タグUIの実装は、`isScrollEnabledをfalseにしたUICollectionView` で実装しています。
これはあくまで一例なので、もっといい方法があればぜひコメントいただければと思います。

# 気をつける点
ただUICollectionViewのUIをTableViewに持たせるだけなのであれば比較的簡単かと思われますが、iOSは画面回転などでCollectionViewの行数が変わり、当然CollectionViewのサイズ自体が変わります。
iPadに関してはSplit Viewでの表示やSlide Overでの表示でもCollectionViewサイズが変わります。
それらのUIの各種変更にも対応する形で実装していきます。

# 完成図
全体がUITableViewで、UITableViewの中にUICollectionView持ちのセルが含まれています。

|縦|横|
|---|---|
|![Simulator Screen Shot - iPod touch (7th generation) - 2021-07-13 at 17.30.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687960/f91f3048-d8b0-4e76-0961-578a6c7037cf.png)|![Simulator Screen Shot - iPod touch (7th generation) - 2021-07-13 at 17.30.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687960/b5c037d0-6c15-212e-8be5-88480d188709.png)|

# UICollectionViewを持つUITableViewCellの実装
タグ表示用CollectionViewを持つUITableViewCellを実装します。
以下は実装のポイントです。

## 必要なプロパティの準備
以下のプロパティを持ちます。

```swift
    private var observer: NSKeyValueObservation?
    var collectionView: UICollectionView!
```

## 左寄せ用のUICollectionViewFlowLayoutの用意
CollectionViewに左寄せ用のUICollectionViewFlowLayoutを適用します。
https://stackoverflow.com/questions/22539979/left-align-cells-in-uicollectionview
有志が各種左寄せ用レイアウトを用意しているので、それを使いましょう。
このとき、CollectionViewFlowLayoutの`estimatedItemSize`は`UICollectionViewFlowLayout.automaticSize`に設定しておくことを忘れないでください。

## systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority)のオーバーライド
`func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize`
をオーバーライドして、collectionView.collectionViewLayout.collectionViewContentSizeを返します。

```swift
    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return collectionView.collectionViewLayout.collectionViewContentSize
    }
```

## TableViewのcellForRowAt用のデータ処理時にcontentSizeのobserveを作成する

例えば`configure` などのデータ処理用メソッドを用意して、`func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell` の実行時に呼び出した場合、configure内部でobserverを作成し、その後データ反映処理を行います。

observerはCollectionViewのContentSizeが変化するたびに動作します。
ContentSizeが変わったら、CollectionViewを最新の高さに対応するため、tableViewのreloadData()をコールします。

このとき、tableViewのcellForRowAtは仕組み上多重に動作するので、データをチェックして、違うデータの時のみCollectionViewのreloadData()を実行します。


```swift
    private var array: [String] = []

    func configure(array: [String]) {
        observer = collectionView.observe(\.contentSize, options: [.new]) { collectionView, change in
            if (change.newValue?.height) != nil {
                // コンテンツのサイズが画面回転やデータによって変更するたび、TableViewを再描画する
                self.tableView?.reloadData()
            }
        }
        // データが前回のものと同じ場合、collecviewViewのreloadDataを行わない
        if self.array != array {
            self.array = array
            collectionView.reloadData()
        }
    }

    // UICollectionViewDataSource
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        array.count
    }


// https://stackoverflow.com/questions/15711645/how-to-get-uitableview-from-uitableviewcell
extension UITableViewCell {
    var tableView: UITableView? {
        var view = superview
        while let v = view, v.isKind(of: UITableView.self) == false {
            view = v.superview
        }
        return view as? UITableView
    }
}

```

`prepareForReuse` のときにobserverをnilにしておきます。

```swift
    override func prepareForReuse() {
        super.prepareForReuse()
        observer = nil
    }
```

# まとめ
この記事では、タグ表示用のCollectionView入りUITableViewCellに対して全ての実装を行うことで実現を行なっていますが、ポイントとしては２点あり、

- `systemLayoutSizeFitting` で `collectionView.collectionViewLayout.collectionViewContentSize` を返す
- collectionView.contentSizeが変わったことを何らかの方法で検知し、`TableView.reloadData()` を行う

ということができれば、今回の実装に拘らなくともタグを並べての表示が可能かと思います。