Scott Muddlegeist
Scott Muddlegeist

Reputation: 141

iOS SwiftUI ScrollView in NavigationStack: Scroll To Top Doesn't Finish Expanding Navigation Title, If Animated

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
enter image description here

proxy.scrollTo(0) *no animation - succeeds

enter image description here

Upvotes: 3

Views: 1921

Answers (1)

MatBuompy
MatBuompy

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

Related Questions