LazyがないiOS13の世界でどうページネーション処理を作るか？

この記事は[Diverse Advent Calendar 2020](https://qiita.com/advent-calendar/2020/diverse) 20日目の記事になります:santa:

#この記事について
SwiftUIが発表してから1年近く経ちましたね！
ios14からLazyが登場しSwiftUIでReuseの概念が追加されました！
これによりonAppearをトリガーに次ページを取得しやすいようになったかと思います。

しかし。。。
ios13の世界にはLazyが存在しない中
どのようにページネーション処理を行うのか？について今回書いてみます。
※ios13でListはLazyっぽい挙動な気がする:joy:


# ios14ではどうやる？
Lazyが存在してるので画面に表示されたコンテンツのonAppearで
取得済みのデータと照らし合わせて次のデータを取得するイメージです。

例にはないですがLazyHStack,LazyHGridも同様です。
## LazyVStack

```swift
struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 8) {
                ForEach(0..<20) { number in
                    ListCell(number: number)
                        .onAppear() {
                            print("\(number)")
                        }
                }
            }
        }
    }
}
```

## LazyVGrid

```swift
struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVGrid(columns: Array(repeating: GridItem(), count: 2), spacing: 16) {
                ForEach(0..<20) { number in
                    GridCell(number: number)
                        .onAppear() {
                            print("\(number)")
                        }
                }
            }
        }
    }
}
```

# ios13では？

## List
Listだけはios14のLazyと同様の方法で
onAppearでフックして次のデータを取得する処理を書くことが可能です。

```swift
import SwiftUI
struct ContentView: View {
    var body: some View {
        List {
            ForEach(0..<20) { number in
                ListCell(number: number)
                    .onAppear() {
                        print("\(number)")
                    }
            }
        }
    }
}
```

## VStack
まず最初にios14のLazyVStack同様にonAppearを使って見る場合どうなるか？についてです。

```swift
struct ContentView: View {
    var body: some View {
        ScrollView {
            VStack(spacing: 8) {
                ForEach(0..<20) { number in
                    ListCell(number: number)
                        .onAppear() {
                            print("\(number)")
                        }
                }
            }
        }
    }
}
```

この場合は画面に表示されてないnumberまでもprint出力されてしまいます(0~19):sweat:
これだと次ページを取得するタイミングが画面が表示されてた時になってしまい想定と違うため
ios13のVStackでonAppearをトリガーに次ページを取得するのは難しいかと思います。

## Scroll量をトリガーにする
ios13でLazyを持たないVStack,HStack,Grid([QGrid](https://github.com/Q-Mobile/QGrid)でページネーションを行う例です。

一番下まであとどれくらいか？をトリガーに次のデータを取得するイメージです。

TODO
細かな説明書くと間に合わなさそうなので後ほど書いてきます:bow:

詳細は一旦[Pagination](https://github.com/giiiita/Pagination)リポジトリをのぞいていただけますと幸いです:bow:

```swift
struct ContentView: View {
    @State var contentOffset: CGFloat = .zero
    @State var scrollViewContentHeight: CGFloat = .zero
    var body: some View {
        GeometryReader { geometry in
            ScrollTrackerView(contentOffset: self.$contentOffset) {
                VStack(spacing: 8) {
                    ForEach(0..<10) { number in
                        ListCell(number: number)
                    }
                }.modifier(ScrollContentHeightKey())
            }.contentOffsetChanged {
                let maximumOffset: CGFloat = self.scrollViewContentHeight - geometry.size.height
                let distanceToBottom: CGFloat = maximumOffset - self.contentOffset
                print("\(distanceToBottom)")
            }.onPreferenceChange(ScrollContentHeightKey.self) {
                self.scrollViewContentHeight = $0
            }
        }
    }
}

```

## ios13かつGridの場合
Gridは本家[QGrid](https://github.com/Q-Mobile/QGrid)をForkした[こちら](https://github.com/giiiita/QGrid)を使用していただけると:v:


![スクリーンショット 2020-12-20 15.33.57（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75593/6052266d-a8a0-9957-ea79-e8e257188730.png)

Commit: 0bef369f0e96d46276394aecee1d98580ed7d362
↑こちら指定お願いします


本家と違うのは
contentOffsetChangedで下までの値がvalueとして
返ってくるcallback関数を用意してるところになります。
[Pagination](https://github.com/giiiita/Pagination)
↑Gridの例もこちらのリポジトリに乗せてありますのでよかったらぜひ:blush:

```swift
var body: some View {
    QGrid(self.data,
          columns: 2,
          vSpacing: 24.0,
          hSpacing: 15.0,
          contentOffsetChanged: { value in
            print("\(value)")
          }) { data in
            Button(
                action: {
                },
                label: {
                    GridCell(name: data.name)
                }
            ).buttonStyle(PlainButtonStyle())
    }
}
```

# まとめ
ページネーションするかどうかの判定について
ios14ではLazyが使えるので<font color="Red">onAppear</font>
ios13ではListはonAppear,VStackなどLazyがないViewに関しては<font color="Red">スクロール量</font>を使うと良いかと思います！

他にもこういう方法があるよっていうのあれば知りたいのでコメントいただけると幸いです:smile: