LoginSignup
49

More than 5 years have passed since last update.

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

Posted at

デフォルトの写真アプリ

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49