【Swift UI】ScrollViewのスクロール量や位置を取得する方法
この記事からわかること
- Swift UIでScrollViewのスクロール量や位置を取得する方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
Swift UIでScrollViewのスクロール量や位置を取得する方法
Swift UIでScrollViewのスクロール量や位置を取得するには以下のように実装します。
struct ScrollOffsetYPreferenceKey: PreferenceKey {
static var defaultValue: [CGFloat] = [0]
static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
value.append(contentsOf: nextValue())
}
}
struct ContentView: View {
@State private var offsetY: CGFloat = 0
@State private var initOffsetY: CGFloat = 0
var body: some View {
ScrollView {
VStack {
ForEach(1...100, id: \.self) {
Text("Item \($0)")
.padding()
}
}.background(
GeometryReader { geometry in
Color.clear
.preference(
key: ScrollOffsetYPreferenceKey.self,
value: [geometry.frame(in: .global).minY]
).onAppear {
initOffsetY = geometry.frame(in: .global).minY
}
}
)
}
.onPreferenceChange(ScrollOffsetYPreferenceKey.self) { value in
offsetY = value[0]
print(offsetY - initOffsetY)
}
}
}
ポイント
- Preference ・・・スクロール量をクラスのプロパティまで渡す
- GeometryReader・・・スクロール量を取得する
ポイントとなるのは上記の2つです。GeometryReader
でスクロール量を取得するためにbackground
の中でColor.clear
を実装してそこからpreference
メソッドで値を渡しています。Color.clear
オブジェクトが背景色としてスクロールビューと同じ大きさに広がってくれているのでそのminY
を取得しています。
しかしこれではスクロールするとスクロールの途中で0を通過する実装になってしまいます。これを避けるために初期値のminY
を保持しておき、出力する際に減算することでスクロール量を表現しています。負の数が気になる場合はabs
で絶対値を取得すればOKです。
59.0
45.0
34.666666666666664
20.66666666666667
16.0
10.333333333333336
2.6666666666666643
-5.333333333333329
-14.0
-32.33333333333334
-43.66666666666666
-49.33333333333334
-53.33333333333334
-55.66
実装失敗例
私は最初以下のように実装しました。これでいけるかと思いましたがScrollView
とGeometryReader
の相性が悪く、スクロールビューなのにスクロールできなくなってしまいました。
var body: some View {
ScrollView {
GeometryReader { geometry in
VStack {
ForEach(1...100, id: \.self) {
Text("Item \($0)")
.padding()
}
}.preference(
key: ScrollOffsetYPreferenceKey.self,
value: [geometry.frame(in: .global).minY]
)
}
}
.onPreferenceChange(ScrollOffsetYPreferenceKey.self) { value in
offsetY = value[0]
print(offsetY - initOffsetY)
}
}
ScrollView
とGeometryReader
を使用する際にスクロール機能を活かすためにはGeometryReader
側が親になるように配置しないといけないようですが、これではスクロール量を取得することができませんでした。
GeometryReader { geometry in
ScrollView() {
VStack {
ForEach(1...100, id: \.self) {
Text("Item \($0)")
.padding()
}
}
}.preference(
key: ScrollOffsetYPreferenceKey.self,
value: [geometry.frame(in: .global).minY]
)
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。