swiftui

ScrollViewProxy scrollTo() does nothing if the list is scrolling


I have a list wrapped in a ScrollViewReader and have overlayed a button (updated to provide a complete demo)

struct Rows: View {
    
    var dataRows: [RowData] = []
    
    init() {
        for i in 0...1000 {
            dataRows.append(RowData(id: i))
        }
    }
    
    var body: some View {
        ScrollViewReader { scrollView in
            ZStack(alignment: .bottomTrailing) {
                List(dataRows) { row in
                    NavigationLink {
                    } label: {
                        Row(data: row)
                    }
                }
                Button("Scroll To Top") {
                    print("button pressed")
                    scrollView.scrollTo(0)
                }
                .buttonStyle(.bordered)
            }
        }
    }
}

struct Row: View {
    var data: RowData
    
    var body: some View {
        Text(String(data.id))
    }
}

struct RowData: Identifiable {
    var id: Int
}

This all works as expected, I tap the button and the list is scrolled to row with id 0

However if I flick the list, it scrolls and slowly decelerates over several seconds, during the time if I press the button the message is printed, but the list doesn't scroll back.

scrollTo only seems to work if the list has come to a complete stop

How can I halt the scrolling to get the button to work?

I can reproduce this in the simulator, but it sometimes works as I'd like - however on a real device (iPhone 12, iOS 17) the problem is always seen

Many thanks!


Solution

  • I don't think this is the clearest solution, but we can create additional @State variable and use it with .onChange modifier.

    struct ContentView: View {
        
        @State private var shouldScrollToItem = false
        
        var body: some View {
            ScrollViewReader { scrollViewProxy in
                ScrollView {
                    Button("Scroll") {
                        shouldScrollToItem.toggle()
                    }
                    ...
                }
                .onChange(of: shouldScrollToItem) {
                    scrollViewProxy.scrollTo(id)
                }
            }
        }
    }