UICollectionViewを簡単に使うためのテンプレート

## 環境
swift version5.4

## コード
### 下準備

```swift
import UIKit

extension UIView {

    /// クラス名を文字列で返す
    static var className: String { String(describing: Self.self) }
}
```

```swift
import UIKit

extension UICollectionView {

    func register<T: UICollectionViewCell>(type: T.Type) {
        let nib = UINib(nibName: T.className, bundle: nil)
        register(nib, forCellWithReuseIdentifier: T.className)
    }

    func dequeueReusableCell<T: UICollectionViewCell>(withReuseCell type: T.Type, for indexPath: IndexPath) -> T {
        let cell = dequeueReusableCell(withReuseIdentifier: T.className, for: indexPath) as! T
        return cell
    }
}
```

```swift

import UIKit

/// UICollectionViewのカラムレイアウトを作成するためのstruct
struct ColumnSetting {

    let column: CGFloat
    let verticalMargin: CGFloat
    let sideMargin: CGFloat
    let interMargin: CGFloat
    let aspectRatio: CGFloat

    /// 指定のカラム数のレイアウトを作成するための設定
    /// - Parameters:
    ///   - column: カラム数
    ///   - verticalMargin: 上下の余白。初期値0。
    ///   - outerMargin: 左右の余白。初期値0。
    ///   - interMargin セル間の余白。初期値0。
    ///   - aspectRatio: 横を1としたときの縦の比率。初期値1。
    init(column: Int, verticalMargin: CGFloat = 0,
         sideMargin: CGFloat = 0, interMargin: CGFloat = 0, aspectRatio: CGFloat = 1) {
        self.column = CGFloat(column)
        self.verticalMargin = verticalMargin
        self.sideMargin = sideMargin
        self.interMargin = interMargin
        self.aspectRatio = aspectRatio
    }

    /// UICollectionViewインスタンスのcollectionViewLayoutプロパティに代入する
    func collectionViewLayout() -> UICollectionViewFlowLayout {
        let layout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: verticalMargin, left: sideMargin,
                                           bottom: verticalMargin, right: sideMargin)
        layout.minimumInteritemSpacing = interMargin
        return layout
    }

    /// UICollectionViewDelegateFlowLayoutのsizeForItemAtメソッドの返却値として設定する
    func sizeForItem(parent: UICollectionView) -> CGSize {
        let totalSideMargin: CGFloat = sideMargin * 2
        let intervalCount: CGFloat = column - 1
        let totalInterMargin = interMargin * intervalCount
        let totalMargin = totalSideMargin + totalInterMargin
        let availableWidth = parent.frame.width - totalMargin
        let baseCellSize = floor(availableWidth / column)
        return CGSize(width: baseCellSize, height: baseCellSize * aspectRatio)
    }
}
```

### 呼び出し

```swift

import UIKit

final class SampleViewController: UIViewController {

    typealias Cell = SampleCollectionViewCell
    private let columnSetting = ColumnSetting(column: 3, sideMargin: 4, interMargin: 4)

    @IBOutlet private weak var collectionView: UICollectionView! {
        didSet {
            collectionView.delegate = self
            collectionView.dataSource = self
            collectionView.register(cell: Cell.self)
            collectionView.collectionViewLayout = columnSetting.collectionViewLayout()
        }
    }
}

// MARK: - UICollectionViewDelegate

extension SampleViewController: UICollectionViewDelegate {

}

// MARK: - UICollectionViewDelegateFlowLayout

extension MatchingViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        return columnSetting.sizeForItem(parent: collectionView)
    }
}

// MARK: - UICollectionViewDataSource

extension SampleViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView,
                        numberOfItemsInSection section: Int) -> Int {
        return 0
    }

    func collectionView(_ collectionView: UICollectionView,
                        cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseCell: Cell.self, for: indexPath)
        return cell
    }
}
```

## 参考
[UICollectionViewの列数と行数を端末サイズ関係なく実装する](https://tokizuoh.dev/posts/bx1rn3hnd6cjghqh/)
[【Swift】sectionHeadersPinToVisibleBoundsを設定するとスクロールがカクつく不具合修正](https://blog.shota-ito.com/2021/04/01/post-687/)