【Swift UI】ScrollViewのスクロール量や位置を取得する方法

【Swift UI】ScrollViewのスクロール量や位置を取得する方法

この記事からわかること

  • Swift UIScrollViewスクロール位置取得する方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

【Swift UI】ScrollViewのスクロール量や位置を取得する方法

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)
        }
    }
}

ポイント

ポイントとなるのは上記の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

実装失敗例

私は最初以下のように実装しました。これでいけるかと思いましたがScrollViewGeometryReaderの相性が悪く、スクロールビューなのにスクロールできなくなってしまいました

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)
    }
}

ScrollViewGeometryReaderを使用する際にスクロール機能を活かすためには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]
    )
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index