はじめに
WWDC2020開幕しましたね![]()
昨年発表されたSwiftUIから約1年経ちSwiftUIもパワーアップしてる感出てきたかと!
今回は追加された ScrollViewReaderについて書いてみようかと思います。
ScrollViewReader
A view whose child is defined as a function of a ScrollViewProxy targeting the scrollable views within the child.
正直最初見たときは全然わかりませんでした![]()
動作見て僕なりな言葉でいうと
ScrollView内に含まれる子要素を画面の指定箇所にスクロールしたい場合これ使おうになります![]()
まずシンプルな状態なコードです。
import SwiftUI
struct ContentView: View {
    var body: some View {
        ScrollView {
            ScrollViewReader { value in
                ForEach(0..<100) { index in
                    Text("\(index)")
                        .frame(width: 300, height: 300)
                        .background(Color.green)
                        .id(index)
                }
            }
        }
    }
}
ScrollViewReaderのclosureで返ってきてるvalueの型はScrollViewProxyです。
ScrollViewProxyはscrollToという関数を持っています。
func scrollTo<ID>(ID, anchor: UnitPoint?)
Text("\(index)")
    .frame(width: 300, height: 300)
    .background(Color.green)
    .id(index)
ScrollViewの子要素にはidがsetされており
ScrollViewProxyのscrollToで指定の子要素のidを入れてあげれば指定要素までスクロールします。
指定する要素を画面上のどこまでスクロールさせるのかは第二引数のanchorで決めれば大丈夫です。
例えばidが10の子要素を画面上まで移動させたい場合は以下になります。
func scrollTo<ID>(10, anchor: .top)
指定した子要素を画面一番上までスクロールさせるコードになります。
どれだけスクロールしても右上のボタンを押せば画面を開いた場合と同じ画面になります。
struct ContentView: View {
    @State var value: ScrollViewProxy?
    var body: some View {
        ZStack {
            ScrollView {
                ScrollViewReader { value in
                    LazyVStack(alignment: .center, spacing: 16) {
                        ForEach(0..<100) { index in
                            Text("\(index)")
                                .frame(width: 200, height: 200)
                                .background(Color.green)
                                .id(index)
                        }
                    }.onAppear {
                        self.value = value
                    }
                }
            }
            VStack {
                HStack {
                    Spacer()
                    Button(action: {
                        withAnimation {
                            self.value?.scrollTo(0, anchor: .top)
                        }
                    }, label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color.yellow)
                                .frame(width: 60, height: 60)
                            Text("top")
                                .foregroundColor(Color.black)
                        }
                    }).padding(.trailing, 16)
                }
                Spacer()
            }
        }
    }
}
実際に試してみたい方は以下リンクよりcloneしていただけると幸いです![]()
SampleScrollViewReader
まとめ
ScrollView内に含まれる子要素を画面の指定箇所にスクロールしたい場合
例えば一番下までスクロールしたけど一番上まで戻る際などにはScrollViewReaderを使うと良さそうです![]()
補足
まだ調査中なところではありますが、
どこまでスクロールしているのかをidで知ることは可能っぽい?です。
LazyVStack(alignment: .center, spacing: 16) {
    ForEach(0..<100) { index in
        Text("\(index)")
            .frame(width: 200, height: 200)
            .background(Color.green)
            .id(index)
            .onAppear {
                print("\(index)")
            }
    }
}
以前のVStackでは0~99のindexが画面表示時にprint出力されましたが、
LazyVStackの場合はスクロールしてViewが表示された際にそのViewのidがprint出力されてる感じがします。
以前のSwiftUIでいうListの挙動と同じように
onAppearでどこまで表示しているのかがハンドリングできそう![]()