縦横両方向にスクロールできるビューを作ってみる

# はじめに
縦と横の両方向にスクロールしたいときありますよね？
そんなときに、TableViewCellにCollectionViewを設置することで実現できました。

## 完成イメージ
![output.gif](https://qiita-image-store.s3.amazonaws.com/0/71694/ce6579c4-b4e5-60f1-084b-fa62921b9b03.gif)

# 実装サンプル
実装手順は、下記のとおりです。

1. レイアウトを作成する
2. TableViewCellのカスタムクラスを定義する
3. TableViewCell中に設置するCollectionViewのCellのカスタムクラスを定義する
4. 利用の仕方

## 1. レイアウトを作成する
レイアウトは、下記のような階層構造です。
ポイントは、TableViewCell内にCollectionViewを設置している点です。

![スクリーンショット 2016-09-08 10.41.22.png](https://qiita-image-store.s3.amazonaws.com/0/56176/94550d76-ec98-e933-1701-3edcbf12108f.png "スクリーンショット 2016-09-08 10.41.22.png")

| クラス名 | 説明 |
|---|---|
| TableViewInCollectionViewCell | TableViewCellのカスタムクラス名 |
| CollectionViewCell | TableViewCell中に設置するCollectionViewCellのカスタムクラス名 |
| ViewController | 画面クラス名 |

なお、CollectionViewのScroll Directionは、
横にスクロールするために「Horizontal」を指定しています。

## 2. TableViewCellのカスタムクラスを定義する
TableViewCell内で横スクロールできるようにしておきます。
また、何行目が呼ばれたか分かるようにtagに行番号を保存しておきます。

```swift:TableViewInCollectionViewCell.swift
import UIKit

class TableViewInCollectionViewCell: UITableViewCell {

    @IBOutlet weak var collectionView: UICollectionView!

    static var identifier: String {
        get {
             return String(self)
        }
    }
}

extension TableViewInCollectionViewCell {

    func setCollectionViewDataSourceDelegate<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>(
        dataSourceDelegate: D, forRow row: Int) {

        collectionView.delegate = dataSourceDelegate
        collectionView.dataSource = dataSourceDelegate
        collectionView.tag = row
        collectionView.setContentOffset(collectionView.contentOffset, animated: false)
        collectionView.reloadData()
    }

    var collectionViewOffset: CGFloat {
        set {
            collectionView.contentOffset.x = newValue
        }

        get {
            return collectionView.contentOffset.x
        }
    }
}
```

## 3. TableViewCell中に設置するCollectionViewのCellのカスタムクラスを定義する
画像を表示するだけのセルを定義します。
自由にカスタマイズしてください。

```swift:CollectionViewCell
import UIKit

class CollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var imageView: UIImageView!

    static var identifier: String {
        get {
            return String(self)
        }
    }
}
```

## 4. 利用の仕方
ポイントは、TableViewを表示したタイミングでCollectionViewの表示及び、スクロールの調整をしている点です。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    let dataSource = ViewController.generateRandomData() //テストデータ
    var offsets = [Int: CGFloat]()
}

extension ViewController: UITableViewDataSource {

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier(TableViewInCollectionViewCell.identifier,
                                                               forIndexPath: indexPath)
        return cell
    }
}

extension ViewController: UITableViewDelegate {

    //TableViewCellが表示されたときに、CollectionViewの描画する
    func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,
                   forRowAtIndexPath indexPath: NSIndexPath) {

        guard let tableViewCell = cell as? TableViewInCollectionViewCell else {
            return
        }

        tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
        tableViewCell.collectionViewOffset = offsets[indexPath.row] ?? 0
    }

    func tableView(tableView: UITableView,
                   didEndDisplayingCell cell: UITableViewCell,
                   forRowAtIndexPath indexPath: NSIndexPath) {

        guard let tableViewCell = cell as? TableViewInCollectionViewCell else {
            return
        }

        offsets[indexPath.row] = tableViewCell.collectionViewOffset
    }
}

extension ViewController: UICollectionViewDataSource {

    func collectionView(collectionView: UICollectionView,
                        numberOfItemsInSection section: Int) -> Int {

        return dataSource[collectionView.tag].count
    }

    func collectionView(collectionView: UICollectionView,
                        cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(CollectionViewCell.identifier,
                                                                         forIndexPath: indexPath) as! CollectionViewCell
        cell.imageView.image = dataSource[collectionView.tag][indexPath.item]
        return cell
    }
}

extension ViewController: UICollectionViewDelegate {

    //タップされたときに、縦が何行目、横が何マス目か判断する
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        print("Line : \(collectionView.tag) / section : \(indexPath.section) / row : \(indexPath.row)")
    }
}

// 画像表示用のダミーデータを取得する機能
extension ViewController {

    class func randomImage() -> UIImage {

        let fileNameIndex = arc4random() % 2
        let fileName = "image\(fileNameIndex + 1).jpg"
        return UIImage.init(named: fileName)!
    }

    class func generateRandomData() -> [[UIImage]] {
        let numberOfRows = 15
        let numberOfItemsPerRow = 10

        return (0..<numberOfRows).map { _ in
            return (0..<numberOfItemsPerRow).map { _ in randomImage() }
        }
    }
}
```

# まとめ
TableViewCellの中にTableViewを配置する方法も試しましたが、
TableViewとTableViewCellの向きをそれぞれ90度回転しなければ、
表示される向きが期待通りに表示できませんでした。

しかし、CollectionViewは、Scrollの向きを縦 or 横に指定できるため
シンプルに実装できました。

## 参考
https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/

以上です。