Swiftで簡単なGrid Layoutを実装するためのレイアウトクラス (UIKit)


本題

UIKitでGridレイアウトを実装しようとするとUICollectionViewクラスを利用するのが一般的だと思います。
ただご存知の通りUICollectionViewを使う場合、UICollectionViewLayout, UICollectionViewDelegate, UICollectionViewDataSource、などのUICollectionViewのメソッドをいちいち呼ばなきゃいけないので結構面倒くさいです。
実際、昨年ぐらい(もっと前から?)宣言的に書くというのが、じわじわと広がってきて、今年になってSwiftUIもでてきてだいぶ宣言的にかけるようになりました。

CollectionViewはセルを再利用してくれるので、たくさんのデータを表示する際には使うのですが、場合によってはセルを再利用しなくてもいい場合があります。

そんなときにCollectionViewを使わずsubViewを並べてくれるレイアウトクラスをちょこっと作ってやるといいと思います。

再利用しないGridViewの例

たとえば、パスコードロックなどの数字入力画面、カレンダー、カラーパレットなどですね。

使用例1

        gridView.backgroundColor = .white
        gridView.insets = .zero
        gridView.borderWidth = 5
        gridView.gridSize = 4

        let size = gridView.gridSize * gridView.gridSize
        let colors = (0..<size).map { UIColor(hue: CGFloat($0) / CGFloat(size), saturation: 0.85, brightness: 0.9, alpha: 1) }
        colors.forEach {
            let view = UIView()
            view.backgroundColor = $0
            gridView.addSubview(view)
        }
        gridView.layout.center(0).size(300) // オートレイアウトのExtension

カラーパレットとか

カレンダーとか

パスコードの数字入力画面とか

実装

final class GridLayoutView: UIView {
    var gridSize: Int = 4
    var borderWidth: CGFloat = 5
    var insets: UIEdgeInsets = .zero

    override func layoutSubviews() {
        super.layoutSubviews()

        let margin = borderWidth

        let width = (bounds.width - CGFloat(gridSize - 1) * margin - insets.left - insets.right) / CGFloat(gridSize)
        let height = (bounds.height - CGFloat(gridSize - 1) * margin - insets.top - insets.bottom) / CGFloat(gridSize)

        let startX: CGFloat = insets.left
        let startY: CGFloat = insets.top

        var x = startX
        var y = startY

        subviews.enumerated().forEach { index, view in
            view.frame.origin = CGPoint(x: x, y: y)
            view.frame.size = CGSize(width: width, height: height)

            x += width + margin
            if index % gridSize == gridSize - 1 {
                x = startX
                y += height + margin
            }
        }
    }
}

Tips

  • 中央のマージンはborderWidthで指定します。
  • 外側のマージンはinsetsで指定します。
  • ボーダーカラーはbackgroundColorを指定すればかわります。
  • subViewを自動で並べてくれるのでaddSubviewするだけです。

補足

等間隔にViewを配置するにはUIStackViewをよく使いますが、今回は使っていません。
内部でAutoLayoutを使用しているのと、純粋に座標計算したほうがコード量が少ないからです。

Storyboard派の皆さんはUIStackViewを使えば簡単にできるじゃないかと思うかもしれませんが
個人開発する上では圧倒的に時短かつ再利用可能です(コンポネントとして)

まとめ

ちょっとしたGridLayoutを実現するためのレイアウトクラスを作って使ってるよというはなし。
insert, delete, drag, reuseなどUICollectionViewのほうが優れている点が多い。