簡単なGridLayoutを実装するためのLayoutクラス

はじめに

iOS開発をはじめて2年が経ち自分なりにUIKitでいろんなUIを作ってきました。そろそろアウトプットとしてgoogle検索で上位でてくる古い情報を上書きしようとと思っていた矢先、SwiftUIがでてきてしまい一気にモチベーションが下がってしまいました。ただなんかもったいなく感じたため1日10分程度をアウトプットの時間に割いていこうとおもいます。SwiftUIはあと1年遅くてもよかったかなぁ。

本題

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

カラーパレットとか

カレンダーとか

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

こうゆう画面をCollectionViewで実装しようとすると少し大袈裟というかコード量が増えちゃいますよね。

実装

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のほうが優れている点が多い。