ScrollViewReader는 스크롤링이 감지되거나 자동으로 위치 이동이 필요할 때 사용하는 뷰로써, ScrollViewProxy라는 것을받아서 스크롤 기능을 수행할 수 있도록 해주는 뷰입니다.
@Namespace var topID
@Namespace var bottomID
var body: some View {
ScrollViewReader { proxy in
ScrollView {
Button("Scroll to Bottom") {
withAnimation {
proxy.scrollTo(bottomID)
}
}
.id(topID)
VStack(spacing: 0) {
ForEach(0..<100) { i in
color(fraction: Double(i) / 100)
.frame(height: 32)
}
}
Button("Top") {
withAnimation {
proxy.scrollTo(topID)
}
}
.id(bottomID)
}
}
}
func color(fraction: Double) -> Color {
Color(red: fraction, green: 1 - fraction, blue: 0.5)
}
위의 예제는 공식문서에 작성되어 있는 예제 코드입니다.
ScrollViewReader는 proxy를 받고, 이 proxy는 scrollTo라는 것을 프로퍼티로 갖고 있는 것을 알 수 있습니다.
그리고 srrollTo() 메서드에 각 button의 id값을 넘겨주는 것을 알 수 있는데, 이 작업을 통해 버튼을 클릭시 자동으로 스크롤 되는 것을 알 수 있습니다.
여기서 중요한 점은 ScrollViewProxy를 컨텐트 뷰 빌더가 실행되는 동안에 사용하면 안됩니다. 컨텐트 뷰 빌더가 실행하는 도중에 사용하게 되면 런타임 에러를 발생할 것입니다. 대신에, 제스처 핸들러나 onChange 메서도와 같이 컨텐트 내에서 생성된 액션에 대해서만 프록시를 호출 할 수 있습니다.
struct ContentView: View {
@Namespace var topID
@Namespace var bottomID
@State var isTapped: Bool = false
var body: some View {
ScrollViewReader { proxy in
ScrollView {
Button("Scroll to Bottom") {
withAnimation {
// isTapped.toggle()
proxy.scrollTo(bottomID)
}
}
.id(topID)
VStack(spacing: 0) {
ForEach(0..<100) { i in
color(fraction: Double(i) / 100)
.frame(height: 32)
if isTapped {
VStack {
Text("이상한걸 보여드리겠습니다.")
Text("눈뜨고 지켜봐주세요!")
Text("버튼은 어디에..?")
}
}
}
}
Button("Top") {
withAnimation {
proxy.scrollTo(topID)
}
}
.id(bottomID)
}
}
}
}
func color(fraction: Double) -> Color {
Color(red: fraction, green: 1 - fraction, blue: 0.5)
}
이와 같이 scrollViewProxy가 실행되고 있는 도중에 VStack이라는 새로운 뷰를 그리려 하니 뷰가 깨지는 것을 볼 수 있습니다.
이는 LazyVStack을 사용하면 해결이 가능합니다.
LazyVStack(spacing: 0) {
ForEach(0..<100) { i in
color(fraction: Double(i) / 100)
.frame(height: 32)
if isTapped {
VStack {
Text("이상한걸 보여드리겠습니다.")
Text("눈뜨고 지켜봐주세요!")
Text("버튼은 어디에..?")
}
}
}
}
ScrollViewProxy는 구조체입니다. 직접적으로 인스턴스를 생성하지 않고, 컨텐트 뷰 빌더 안에서 받습니다. 그리고 이 안에 scrollTo 메서드가 존재합니다.
scrollTo의 기본적인 선언은 위와 같습니다.
위치를 이동시킬 id와 어디로 이동할지를 알기 위한 anchor가 매개변수로 있습니다.
anchor을 따로 설정해 주지 않는다면 nil로 지정이 되고, 각 아이디의 위치에 알맞게 이동하게 됩니다.
출처
https://green1229.tistory.com/295
https://developer.apple.com/documentation/swiftui/scrollviewreader https://developer.apple.com/documentation/swiftui/scrollviewproxy https://developer.apple.com/documentation/swiftui/scrollviewproxy/scrollto(_:anchor:)