この記事はDiverse Advent Calendar 2020 20日目の記事になります
#この記事について
SwiftUIが発表してから1年近く経ちましたね!
ios14からLazyが登場しSwiftUIでReuseの概念が追加されました!
これによりonAppearをトリガーに次ページを取得しやすいようになったかと思います。
しかし。。。
ios13の世界にはLazyが存在しない中
どのようにページネーション処理を行うのか?について今回書いてみます。
※ios13でListはLazyっぽい挙動な気がする
ios14ではどうやる?
Lazyが存在してるので画面に表示されたコンテンツのonAppearで
取得済みのデータと照らし合わせて次のデータを取得するイメージです。
例にはないですがLazyHStack,LazyHGridも同様です。
LazyVStack
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 8) {
ForEach(0..<20) { number in
ListCell(number: number)
.onAppear() {
print("\(number)")
}
}
}
}
}
}
LazyVGrid
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でフックして次のデータを取得する処理を書くことが可能です。
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を使って見る場合どうなるか?についてです。
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)
これだと次ページを取得するタイミングが画面が表示されてた時になってしまい想定と違うため
ios13のVStackでonAppearをトリガーに次ページを取得するのは難しいかと思います。
Scroll量をトリガーにする
ios13でLazyを持たないVStack,HStack,Grid(QGridでページネーションを行う例です。
一番下まであとどれくらいか?をトリガーに次のデータを取得するイメージです。
TODO
細かな説明書くと間に合わなさそうなので後ほど書いてきます
詳細は一旦Paginationリポジトリをのぞいていただけますと幸いです
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をForkしたこちらを使用していただけると
Commit: 0bef369f0e96d46276394aecee1d98580ed7d362
↑こちら指定お願いします
本家と違うのは
contentOffsetChangedで下までの値がvalueとして
返ってくるcallback関数を用意してるところになります。
Pagination
↑Gridの例もこちらのリポジトリに乗せてありますのでよかったらぜひ
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が使えるのでonAppear
ios13ではListはonAppear,VStackなどLazyがないViewに関してはスクロール量を使うと良いかと思います!
他にもこういう方法があるよっていうのあれば知りたいのでコメントいただけると幸いです