スクローラブルと認識できるデザイン
まず初めに、iOSに限らずですがスクロール可能と認識できるデザインがあります。
こんな感じです。2つのセルが完全に表示されていて、残り1つは半分ほど出ているデザインです。
これはユーザーが見た時に「あ、横にスクロールできるんだな」と直感的に思わせるためですね。
これをみた時にCollectionViewの高さとセルのサイズってどうするんだろうと思ってました
よく考えてみるとある程度条件を絞って算数の力を使えば導き出せることに気づいたので実装してみます(正確には中学生で習う1次方程式ですが、ほぼ算数なので許してください)。
計算してみる
まずは手元で計算してみます。
条件を整理して実際に数式を出すところまでやります。
条件の整理
今回は事前に計算をして計算結果を実装に当てはめる、ということが必要なのである程度の条件が必要となります。
以下が条件です。
- 各セルの縦横比が確定している
- 各セル同士のスペースが確定している
- CollectionViewのwidthが確定している
図にするとこんな感じです。
そこまで条件が多いわけではないので、大体の場合で使えるかなーという感じはしてます。
今回は計算を楽にするためにパディングは上下と一番左を同じ値にしてセル間をその2倍としています。
数式を出す
条件が出たのでそれを数式に落とし込んでみましょう。
セルの比
まずは1つ目の条件 各セルの縦横比が確定している
です。
これは比をそのまま計算式にするだけです。
このように定義すると
\begin{aligned}
a:b = w:h => h = \frac{b}{a}w
\end{aligned}
$h$を$w$で表せました。
CollectionViewのwidth
次に2つ目と3つ目の条件を使うとCollectionViewの横幅を表すことができます。
先ほどの図を見るとわかりやすいですが、$W$はセルの横幅の合計とパディングの合計を足した値になるのがわかると思います。
まずセルの横幅の合計は単純にセルの横幅
×個数
なので$N \times w$ですね。
次にパディングの合計です。図の場合であると$p + 2p + 2p$で$5p$ですね。
これを一般的なセルの個数で表現すると、
最初のパディング(p)
+ セルの個数の小数点を切り捨てた整数
× $2p$
になります。
数式にするとこのようになります。
\begin{aligned}
(2\lfloor N \rfloor + 1)p
\end{aligned}
ここでの $\lfloor N \rfloor$ はガウス記号です。これは小数点を切り下げするだけなので例えば3.5や3.8であれば3になります。
これで出揃ったので、先ほどのセルの横幅の合計を足してあげると$W$は
\begin{aligned}
W = (2\lfloor N \rfloor + 1)p + Nw
\end{aligned}
と表せます。
CollectionViewのheight
最後にCollectionViewのセルの高さも出してあげます。
これは上下のパディングとセルの高さを足してあげればいいだけなので、
\begin{aligned}
H = 2p + h
\end{aligned}
と表せます。
これで全ての数式が揃いましたので、まとめてみます。
\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$が求まります。
\begin{equation}
w = \frac{W - (2\lfloor N \rfloor + 1)p}{N}
\end{equation}
次に一番上の式と$w$を使えば$h$が求まります。
\begin{equation}
h = \frac{b}{a}\frac{W - (2\lfloor N \rfloor + 1)p}{N}
\end{equation}
最後に3番目の式と$h$を使えば$H$が求まります。
\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$が以下のように出ます。
\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の高さから設定します。
CollectionViewをストーリーボード上に配置し、top, right, leftを親に合わせていきます。
これで$W$を画面の横幅と一致させています。
親のViewのheightとCollectionViewのheightをアスペクト比で合わせます。
1つ前で親のheightを指定していることに違和感があったかもしれないですが、さっきの段階でheightをwidthに合わせる、みたいなことができないみたいようです。しょうがなく一旦親のViewのheightに合わせていました(もし知っている方がいたら教えて欲しいです )。
そういう理由もありここでheightからwidthに変更します。
最後に数式の出番です。
ここでは $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$です
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
}
ビルドするとこんな感じになります。
ちゃんとセルが2.5個でそれぞれのパディングが取れていますね。
https://github.com/hiromu21/CalcCollectionView
GitHubにソースは載せてあるので良かったらそちらも見てください。
まとめ
ちゃんと計算したかったので、ガウス記号とかを取り入れました。それっぽい数式になって良かったです笑
ちなみにもっと簡単な方法を知っている方がいたら教えてください
最後に注意事項としてこのままのやり方だと問題点があります。それは画面回転した時の挙動です。
実際に実装してみて回転させると分かるのですが、セルが2行で表示されたりセルの大きさが変わったりしてしまいます。
そもそも縦固定が想定されてる実装になっているのでしょうがないのですが
もし機会があれば画面回転にも対応した実装をして記事にしてみたいです!