UICollectionViewで等幅正方形のcellを並べるのを楽にしてみる

  • 43
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

デフォルトの写真アプリ

Simulator Screen Shot 2016.01.31 19.04.33.png

のように、横幅を等分した長さで正方形のcellを並べる場合に、
itemSizeを計算したり、cellcellの間に隙間入れる場合を考慮したり、sectionInsetも反映させたい...となると、結構面倒だったりします。
決め打ちでも良いのですが、collectionViewの横幅がAuto-Layoutで変動する場合に太刀打ち出来ないし...
基本画面横いっぱいにcollectionViewを拡げるんだし画面の横幅をUIScreenから取得して計算すればいいじゃん!ってのもありますが、そうでないケースもあるのでできれば避けたい。

ということで、少しでも面倒なことを考えず楽できるようにextensionを書いてみました。

sample.gif

特徴

  • 複雑な計算を記述する必要がなくなる
  • 処理を呼べば即座にレイアウトが変更される
  • sectionInsetを指定しなければ、spaceで指定した値をinsetの全要素に入れるので、全てにおいて間隔が一定になります。
  • collectionViewscrollDirectionVerticalなら横幅を、Horizontalなら縦幅を基準にして等分します。
  • ※明らかにspaceの値が大きかったり、perRowの値が0以下の場合はレイアウト処理をしません。

実装

こんな感じです。

extension
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対応はしてあるのでライブラリとして導入できます。