デフォルトの写真アプリ
のように、横幅を等分した長さで正方形のcellを並べる場合に、
itemSizeを計算したり、cellとcellの間に隙間入れる場合を考慮したり、sectionInsetも反映させたい...となると、結構面倒だったりします。
決め打ちでも良いのですが、collectionViewの横幅がAuto-Layoutで変動する場合に太刀打ち出来ないし...
基本画面横いっぱいにcollectionViewを拡げるんだし画面の横幅をUIScreenから取得して計算すればいいじゃん!ってのもありますが、そうでないケースもあるのでできれば避けたい。
ということで、少しでも面倒なことを考えず楽できるようにextensionを書いてみました。
特徴
- 複雑な計算を記述する必要がなくなる
- 処理を呼べば即座にレイアウトが変更される
- sectionInsetを指定しなければ、spaceで指定した値をinsetの全要素に入れるので、全てにおいて間隔が一定になります。
- collectionViewのscrollDirectionがVerticalなら横幅を、Horizontalなら縦幅を基準にして等分します。
- ※明らかにspaceの値が大きかったり、perRowの値が0以下の場合はレイアウト処理をしません。
実装
こんな感じです。
import UIKit
public extension UICollectionView {
public func adaptBeautifulGrid(numberOfGridsPerRow: Int, gridLineSpace space: CGFloat) {
let inset = UIEdgeInsets(
top: space, left: space, bottom: space, right: space
)
adaptBeautifulGrid(numberOfGridsPerRow, gridLineSpace: space, sectionInset: inset)
}
public func adaptBeautifulGrid(numberOfGridsPerRow: Int, gridLineSpace space: CGFloat, sectionInset inset: UIEdgeInsets) {
guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
guard numberOfGridsPerRow > 0 else {
return
}
let isScrollDirectionVertical = layout.scrollDirection == .Vertical
var length = isScrollDirectionVertical ? self.frame.width : self.frame.height
length -= space * CGFloat(numberOfGridsPerRow - 1)
length -= isScrollDirectionVertical ? (inset.left + inset.right) : (inset.top + inset.bottom)
let side = length / CGFloat(numberOfGridsPerRow)
guard side > 0.0 else {
return
}
layout.itemSize = CGSize(width: side, height: side)
layout.minimumLineSpacing = space
layout.minimumInteritemSpacing = space
layout.sectionInset = inset
layout.invalidateLayout()
}
}
使ってみる
前提として、collectionViewに、collectionViewFlowLayoutが追加されていることが条件です。
更新したいタイミングで、
// 横に4等分した幅を持つcellでレイアウトする。間隔は2.0px空ける
collectionView.adaptBeautifulGrid(4, gridLineSpace: 2.0)
// 最初に貼った写真アプリとほぼ同様のレイアウトをする。
// 横に4等分した幅を持つcellでレイアウトする。間隔は1.0px空ける
// 更に、sectionInset(top,bottomが10px,left,rightが0.0px)を指定する。
let inset = UIEdgeInsetsMake(10.0, 0.0, 10.0, 0.0)
collectionView.adaptBeautifulGrid(4, gridLineSpace: 1.0, sectionInset: inset)
のように呼んであげればその場で変更されます。
デモでは、指定する値をstepperで変更したときに、上記の処理を呼んで更新しています。
ケースにもよりけりですが、*viewDidLayoutSubviews()やviewDidAppear()*あたりに仕込んでおけば、
Auto-LayoutによってcollectionViewのframeが確定してからレイアウトを設定できるので、
4インチでも4.7インチ、5.5インチでも同様にレイアウトができます。
もし、回転可能な画面で、縦画面と横画面でグリッド数を変えたい場合は、
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.layoutIfNeeded()
let isLandscape = UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation)
let perRow = 4 + (isLandscape ? 2 : 0)
collectionView.adaptBeautifulGrid(perRow, gridLineSpace: 2.0)
}
みたいに書けば、回転した時に縦横でグリッド数を変えられます。
似たような感じで、5.5インチの時だけグリッド数を1増やす、なんかもできるかと思います。
さいごに
ソースとデモはこちらに置いてあります。一応carthage対応はしてあるのでライブラリとして導入できます。