Reputation: 141
I'm needing to implement a scroll-to-top button for a ScrollView living in a NavigationStack, but when I call proxy.scrollTo() (with proxy being the ScrollViewProxy provided by ScrollViewReader), the jump to the top doesn't complete the expansion of the navigation title if it's called in a withAnimation{} block. If proxy.scrollTo() is not in a withAnimation{} block, the jump to top completes as expected. I'm providing simplified code that demonstrates the issue. (*This is not my project. Just a demo of the issue I'm seeing.)
I'm seeing the behavior with XCode 15.2 testing on iOS 16.4, 17.0.1, and 17.2. Device, simulator, and preview.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack() {
LazyVNavView()
.navigationTitle("THE LAZYVNAV")
}
}
}
struct LazyVNavView: View {
var body: some View {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(0..<100) { rowIndex in
Text("Row " + String(rowIndex))
.id(rowIndex)
}
HStack {
Spacer()
Button("To Top: Animation") {
withAnimation{
proxy.scrollTo(0)
}
}
Spacer()
Button("To Top: No Animation") {
proxy.scrollTo(0)
}
Spacer()
}
}
}
}
}
}
#Preview {
ContentView()
}
The issue persists if I:
- designate an anchor for scrollTo()
- use a VStack instead of LazyVStack
- put the to-top button on an overlay
- put the button and list in a ZStack
- dispatch the scrollTo() call to the main thread
- move ScrollViewReader inside ScrollView
- implement a NavigationPath.
The code shown is the simplest way I could come up with to demonstrate the issue. Images of the results after jumping with and without animation attached.
withAnimation { proxy.scrollTo(0) } *using animation - yuck
proxy.scrollTo(0) *no animation - succeeds
Upvotes: 3
Views: 1921
Reputation: 2093
I was able to replicate your issue and found a way to mitigate it. This is likely a bug and you should file a bug report to Apple I think. The way I "solved it" was to use the animation and then move to scroll view to zero with a delay without the animation:
Button("To Top: Animation") {
withAnimation{
proxy.scrollTo(0)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
proxy.scrollTo(0)
}
}
This way the animation completes and it is almost unnoticeable that the last part completes without the animation.
Upvotes: 3