【iOS】算数で横スクロールのCollectionViewの高さを求める


# スクローラブルと認識できるデザイン
まず初めに、iOSに限らずですがスクロール可能と認識できるデザインがあります。

![Blank Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/749533cb-de34-7410-0312-83f3abde7c5e.png)


こんな感じです。2つのセルが完全に表示されていて、残り1つは半分ほど出ているデザインです。
これはユーザーが見た時に「あ、横にスクロールできるんだな」と直感的に思わせるためですね。

これをみた時にCollectionViewの高さとセルのサイズってどうするんだろうと思ってました :thinking: 
よく考えてみるとある程度条件を絞って算数の力を使えば導き出せることに気づいたので実装してみます（正確には中学生で習う1次方程式ですが、ほぼ算数なので許してください）。

# 計算してみる
まずは手元で計算してみます。
条件を整理して実際に数式を出すところまでやります。

## 条件の整理
今回は事前に計算をして計算結果を実装に当てはめる、ということが必要なのである程度の条件が必要となります。
以下が条件です。

1. 各セルの縦横比が確定している
2. 各セル同士のスペースが確定している
3. CollectionViewのwidthが確定している

図にするとこんな感じです。

![Blank Diagram (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/06a9f059-2755-c169-c719-7069cb785809.png)

そこまで条件が多いわけではないので、大体の場合で使えるかなーという感じはしてます。
今回は計算を楽にするためにパディングは上下と一番左を同じ値にしてセル間をその2倍としています。

## 数式を出す
条件が出たのでそれを数式に落とし込んでみましょう。

### セルの比
まずは1つ目の条件 `各セルの縦横比が確定している` です。
これは比をそのまま計算式にするだけです。

![Blank Diagram (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/0c69676f-c3e4-93a2-27f6-6b896f5bee72.png)

このように定義すると

```math
\begin{aligned}
a:b = w:h => h = \frac{b}{a}w
\end{aligned}
```

$h$を$w$で表せました。

### CollectionViewのwidth
次に2つ目と3つ目の条件を使うとCollectionViewの横幅を表すことができます。
先ほどの図を見るとわかりやすいですが、$W$はセルの横幅の合計とパディングの合計を足した値になるのがわかると思います。

![Blank Diagram (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/06a9f059-2755-c169-c719-7069cb785809.png)

まずセルの横幅の合計は単純に`セルの横幅`×`個数`なので$N \times w$ですね。

次にパディングの合計です。図の場合であると$p + 2p + 2p$で$5p$ですね。
これを一般的なセルの個数で表現すると、

`最初のパディング(p)` + `セルの個数の小数点を切り捨てた整数` × $2p$

になります。
数式にするとこのようになります。

```math
\begin{aligned}
(2\lfloor N \rfloor + 1)p
\end{aligned}
```

ここでの $\lfloor N \rfloor$ はガウス記号です。これは小数点を切り下げするだけなので例えば3.5や3.8であれば3になります。

これで出揃ったので、先ほどのセルの横幅の合計を足してあげると$W$は

```math
\begin{aligned}
W = (2\lfloor N \rfloor + 1)p + Nw
\end{aligned}
```

と表せます。

### CollectionViewのheight

最後にCollectionViewのセルの高さも出してあげます。
これは上下のパディングとセルの高さを足してあげればいいだけなので、

```math
\begin{aligned}
H = 2p + h
\end{aligned}
```

と表せます。


これで全ての数式が揃いましたので、まとめてみます。

```math
\begin{aligned}
 h & = \frac{b}{a}w \\
 W & = (2\lfloor N \rfloor + 1)p + Nw \\
 H & = 2p + h \\
\end{aligned}
```

改めて確認すると現時点で判明していない値は$H$, $h$, $w$の3つです。
それでいて式が3つです。つまり3変数の1次方程式なのでそれぞれの値は確定します。

細かい途中式は省きますが、まず簡単なところから求めると2番目の式から$w$が求まります。

```math
\begin{equation}
w = \frac{W - (2\lfloor N \rfloor + 1)p}{N}
\end{equation}
```

次に一番上の式と$w$を使えば$h$が求まります。

```math
\begin{equation}
h = \frac{b}{a}\frac{W - (2\lfloor N \rfloor + 1)p}{N}
\end{equation}
```

最後に3番目の式と$h$を使えば$H$が求まります。

```math
\begin{equation}
H = 2p + \frac{b}{a}\frac{W - (2\lfloor N \rfloor + 1)p}{N} 
\end{equation}
```

これでCollectionViewの高さとセルのサイズが求まりました！
最後に実際に実装してみてどんな感じになるのか見てみます。

# 実装

## 準備
まずは実装の前に条件を満たしておきます。
今回はそれぞれの値を以下のようにしておきます（計算が簡単になるように調整してます）。

$a:b = 8:5$
$p = 16$
$W = 画面の横幅$
$N = 2.5$

すると、$H$, $h$, $w$が以下のように出ます。

```math
\begin{aligned}
 H & = \frac{W}{4} + 12 \\
 h & = \frac{W - 5×16}{4} \\
 w & = \frac{W - 5×16}{2.5} \\
\end{aligned}
```

これで準備は終わりです。

## CollectionViewのheight
準備も終わったのでいよいよ実装です。ここではストーリーボードを使って実装していきます。

まずはCollectionViewの高さから設定します。

![スクリーンショット 2019-12-23 22.22.30（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/29edfc3b-27d6-f53a-533e-5b2e336e5a4d.png)

CollectionViewをストーリーボード上に配置し、top, right, leftを親に合わせていきます。
これで$W$を画面の横幅と一致させています。

![スクリーンショット 2019-12-23 22.22.42（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/275f9a99-1ad2-0b7f-7c61-5fdfb149bc78.png)

親のViewのheightとCollectionViewのheightをアスペクト比で合わせます。

![スクリーンショット 2019-12-23 22.30.25（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/0a70718d-e66e-a089-a967-21ce65720805.png)

1つ前で親のheightを指定していることに違和感があったかもしれないですが、さっきの段階でheightをwidthに合わせる、みたいなことができないみたいようです。しょうがなく一旦親のViewのheightに合わせていました（もし知っている方がいたら教えて欲しいです :pray: ）。
そういう理由もありここでheightからwidthに変更します。

![スクリーンショット 2019-12-23 23.06.32（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/2e62559e-3d87-a86b-48cb-e2b8e40e828f.png)

最後に数式の出番です。
ここでは $H = W/4 + 12$を使います。数式の意味としては$W$を0.25倍して12を足す、です。
なので、`Multipler` には0.25、 `Constant` には12を入れます。

これでCollectionViewのheightの設定は完了です。

## セルサイズの設定

続いてセルサイズの設定もやります。
色々と省略しますが、`viewDidLoad()` で `UICollectionViewFlowLayout` を設定してあげます。

ここで使用するのは $h = (W - 5×16)/4$ と $w = (W - 5×16)/2.5$です

```swift
override func viewDidLoad() {
    super.viewDidLoad()
    let flowLayout = UICollectionViewFlowLayout()

    flowLayout.scrollDirection = .horizontal

    let padding: CGFloat = 16.0
    let screenWidth = UIScreen.main.bounds.width
    // セルのwidthの計算式: w = (W - 5×16)/2.5
    let cellHeight = (screenWidth - 5.0 * padding) / 4
    // セルのheightの計算式: h = (W - 5×16)/4
    let cellWidth = (screenWidth - 5.0 * padding) / 2.5
    flowLayout.itemSize = CGSize(width: cellWidth, height: cellHeight)
    // セル間のパディング
    flowLayout.minimumLineSpacing = padding * 2
    // 上下左右のパディング
    flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)

    collectionView.collectionViewLayout = flowLayout
}
```


ビルドするとこんな感じになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550291/a18f6e56-a59b-3496-2ad5-330cd232f96f.png" height="500">

ちゃんとセルが2.5個でそれぞれのパディングが取れていますね。

https://github.com/hiromu21/CalcCollectionView
GitHubにソースは載せてあるので良かったらそちらも見てください。

# まとめ
ちゃんと計算したかったので、ガウス記号とかを取り入れました。それっぽい数式になって良かったです笑
ちなみにもっと簡単な方法を知っている方がいたら教えてください :pray: 

最後に注意事項としてこのままのやり方だと問題点があります。**それは画面回転した時の挙動です。**
実際に実装してみて回転させると分かるのですが、セルが2行で表示されたりセルの大きさが変わったりしてしまいます。
そもそも縦固定が想定されてる実装になっているのでしょうがないのですが :rolling_eyes: 
もし機会があれば画面回転にも対応した実装をして記事にしてみたいです！