クリスマスまでにSwiftUIでPinterest風のグリッドビューを作りたかったのだけれど...

Qiita Advent Calendar [iOS #2](https://qiita.com/advent-calendar/2019/ios-2) 最終日の記事です。

## :santa::christmas_tree::tada: Merry Christmas :santa::christmas_tree::tada:

今日はクリスマスですね！大人になってしまったので :santa: にプレゼント :gift: をもらう楽しみはなくなってしまいましたが、逆に子どもたちにプレゼントして喜んだ笑顔 :smile: を見るのが楽しみになりました。

さて、最近そのプレゼント選びを便利に、そして楽しくするためにアプリを個人的に作りはじめました。そのアプリのイメージとしては、（僕の大好きな）[Pinterest](https://www.pinterest.jp) のアプリのように、並んだ写真を眺めながら選んでいくようなものを考えています。

せっかく新しく作るアプリなので **SwiftUI** で開発することにしました。まずは一番重要になる Pinterest 風のグリッドビューを SwiftUI で実現する方法をこのアドベンドカレンダーで共有したいと思い、調査を開始しました。

どうやらあの Pinterest 風のグリッドビューは **Staggered grid view (ずらしたグリッドビュー)** なんて呼ばれているみたいです。

## SwiftUI には UIKit の UICollectionView にあたるものがない :cry: 

いきなり出鼻をくじかれました...。

Staggered grid view を以前に UIKit で実装した時は [UICollectionView](https://developer.apple.com/documentation/uikit/uicollectionview) を使いました。なので SwiftUI でもコレクションビュー的な View を探したのですが、ありません（キャッチアップ遅すぎ）。[UITableView](https://developer.apple.com/documentation/uikit/uitableview) にあたるものは [List](https://developer.apple.com/documentation/swiftui/list) として存在しているのに〜 :cry: 

## SwiftUI での実装事例を漁る :mag_right:

調べている間に、SwiftUI で Grid view や Staggered grid view を実装しているような事例をいくつか見つけたので、どんな風に実装されているのか調べてみました。

### SwiftUIExtensions/SwiftUIExtensions - Grid based layouts

<img width="400" alt="staggeredGrid.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16508/db2f9909-304a-4e2b-9f92-d4ce22c7a721.png">

GitHub リポジトリ [SwiftExtensions/SwiftUIExtensions](https://github.com/SwiftExtensions/SwiftUIExtensions) で Grid based layouts として全て同じサイズの Grid view と Staggered grid view の事例がありました！早速どんな風に実装されているのか見てみます。

重要なレイアウトの部分は主に [Sources/SwiftUIExtensions/Layouts/Grid/Grid.swift](https://github.com/SwiftExtensions/SwiftUIExtensions/blob/master/Sources/SwiftUIExtensions/Layouts/Grid/Grid.swift) に書かれていました。そのビュー構造を簡略化すると次の様になっています。

- [ScrollView](https://developer.apple.com/documentation/swiftui/scrollview)
    - [ZStack](https://developer.apple.com/documentation/swiftui/zstack)
        - [ForEach](https://developer.apple.com/documentation/swiftui/foreach)

ZStack はその名の通りビューの Z 軸方向のスタックビューです。この下に ForEach でグリッドの数だけビューを並べていき、そのビューは [GeometryReader](https://developer.apple.com/documentation/swiftui/geometryreader) から得た親ビューのサイズ/位置などの情報を元に計算して求められた場所に配置されています。

（GeometryReader については [SwiftUIの肝となるGeometryReaderについて理解を深める](https://qiita.com/masa7351/items/0567969f93cc88d714ac) でわかりやすく解説されています。）

これは親のビューのジオメトリを元に UIView を座標計算しながら配置していた Auto Layout 以前のレイアウトと同じではないか！？ということは表示するグリッドの数が少し増えただけですぐ限界くるやつでは？

試しにこれのデモアプリの Staggered Grid で表示するグリッドの数を 10,000 にしてシミュレータで実行してみたら、ビューが表示されるまでに **20秒** もかかりました（やっぱりそうだよね...）。一度に100くらいのグリッドなら問題なさそうですが、大量のデータでは実用レベルではないことがわかりました。次！

### Q-Mobile/QGrid

<img width="400" alt="People3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16508/343c3061-fbd1-f35e-a9d9-064ac93e8ba3.png">

SwiftUI でグリッドビューを実現する [Q-Mobile/QGrid](https://github.com/Q-Mobile/QGrid) GitHub リポジトリを見つけました。このリポジトリの description にはこうあります。

> 🎛 QGrid: The missing SwiftUI collection view.

これは期待できそう。Staggered が実現できるかどうかはおいておいて、実装の参考にはなることを期待してコードを読んでみます。このリポジトリのソース構成はとってもシンプルで [Sources/QGrid/QGrid.swift](https://github.com/Q-Mobile/QGrid/blob/master/Sources/QGrid/QGrid.swift) だけです。そのビュー構造を簡略化すると次の様になっていました。


- [ScrollView](https://developer.apple.com/documentation/swiftui/scrollview)
    - [VStack](https://developer.apple.com/documentation/swiftui/vstack)
        - [ForEach](https://developer.apple.com/documentation/swiftui/foreach)
            - [HStack](https://developer.apple.com/documentation/swiftui/hstack)

つまり、垂直方向のスタックビューと水平方向のスタックビューを組み合わせているんですね。なるほど、嫌な予感がします:sweat_drops:いや、でも実は VStack は SwiftUI.List や UIKit.UITableView のように仮想リストビュー的に動いてくれるのかもしれないので念の為検証してみよう、そうしよう。

こちらもリポジトリにデモアプリがあるので、それのグリッドの数を 10,000 にしてシミュレータで実行してみたら、１分経っても何も表示されずに固まってしまいました。やはり VStack や HStack は UIKit の UIStackView 相当のものなんでしょうね（ForEach という繰り返し機構があるのでちょっとだけ期待したのですが）。

## 自分で考えてみるが...

現状、仮想リストビュー的に動作してくれるものが List しかないということがわかったので、この List を使ってなんとかできないか考えてみます。

- List を HStack で複数並べて、各 List に流すデータを制御すればいいのでは！？
- それぞれの List で独立してスクロールすることになってしまうなぁ
- List のスクロールイベントを拾って、それぞれの List のスクロール位置を同期させれば！？
- スクロール位置を知る手段も変更する手段もないらしい...

うーん、うーん、うーん ...

:sunrise: :sunrise: :sunrise: :sunrise:

ということで悩んでいるうちにアドベントカレンダーの投稿日の朝となってしまいました。やはり実現するには SwiftUI としてコレクションビューが提供されることを待つしかなさそうな気がします。表示するグリッドの数が少なければ、紹介したライブラリを使うのもありです。何か良いアイデアがあれば教えていただけると嬉しいです。

今作っているアプリは、とりあえずこの Staggerd grid view の画面については UICollectionView で実装することになりそうです。

それではみなさん、良いクリスマスを :wine_glass: