Reputation: 199
When using a ScrollViewReader
that is passed to another view, the views content moves over when scrolling. If the ForEach
is inside the same view as the reader the view shift does not happen. When you manually scroll the views fix themselves. I have posted a simplified version below.
How would I stop the view from shifting over when the view is being scrolled?
I am using Xcode13 beta 5
struct ContentView: View {
var body: some View {
VStack {
ScrollView {
ScrollViewReader { proxy in
Button("Scroll to bottom") {
withAnimation {
proxy.scrollTo(99, anchor: .bottom)
}
}
TestView(proxy: proxy)
}
}
}
}
}
struct TestView: View {
var proxy: ScrollViewProxy
var body: some View {
VStack { ForEach(1..<100) { index in
Text("Test \(index)")
.id(index)
}
Button("Scroll to top") {
withAnimation {
proxy.scrollTo(1, anchor: .top)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Upvotes: 1
Views: 1375
Reputation: 30341
You can't have the views to scroll to, nested in another view.
Remove the VStack
:
struct TestView: View {
let proxy: ScrollViewProxy
var body: some View {
ForEach(1..<100) { index in
Text("Test \(index)")
.id(index)
}
Button("Scroll to top") {
withAnimation {
proxy.scrollTo(1, anchor: .top)
}
}
}
}
Give the extracted view the content at the top of the ScrollView
, by passing it in:
struct ContentView: View {
var body: some View {
VStack {
TestView { proxy in
Button("Scroll to bottom") {
withAnimation {
proxy.scrollTo(99, anchor: .bottom)
}
}
}
}
}
}
struct TestView<Content: View>: View {
@ViewBuilder let content: (ScrollViewProxy) -> Content
var body: some View {
ScrollView {
ScrollViewReader { proxy in
content(proxy)
ForEach(1..<100) { index in
Text("Test \(index)")
.id(index)
}
Button("Scroll to top") {
withAnimation {
proxy.scrollTo(1, anchor: .top)
}
}
}
}
}
}
I would recommend a slightly different way of doing this. If possible, it is probably best to have this in just one view. However, you want to reuse some of the components so this may not be what you are looking for.
Another thing I noticed: the scrolling doesn't go far enough to reach the buttons, which can appear underneath the safe-area and unable to be pressed. Make these changes:
Button("Scroll to bottom") {
withAnimation {
proxy.scrollTo("bottom", anchor: .bottom)
}
}
.id("top")
Button("Scroll to top") {
withAnimation {
proxy.scrollTo("top", anchor: .top)
}
}
.id("bottom")
Upvotes: 3