SwiftUI's `scrollTo(id:anchor:)` doesn't work if the ScrollView is scrolling

Can somebody tell me if I'm doing something wrong or SwiftUI's scrollTo(id:anchor:) just doesn't work if the ScrollView is scrolling? I have a trivial example that demonstrates the issue:

import SwiftUI

@main struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct Item: Identifiable {
    let id = UUID()
    let timestamp: Date
    let text: String
}

struct ContentView: View {
    
    @State private var items: [Item] = (0...10_000).map{ .init(timestamp: Date(), text: "Row \($0)") }
    @State private var scrollPosition = ScrollPosition(idType: Item.ID.self)
    @State private var newMessage: String = ""
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(items) { item in
                    ItemView(item: item)
                }
            }.scrollTargetLayout()
        }
        .defaultScrollAnchor(.bottom, for: .initialOffset)
        .scrollPosition($scrollPosition, anchor: .bottom)
        .safeAreaBar(edge: .bottom) {
            HStack {
                TextField("Type here", text: $newMessage, axis: .vertical)
                    .textFieldStyle(.roundedBorder)
                Button("Send", action: {
                    let trimmed = newMessage.trimmingCharacters(in: .whitespacesAndNewlines)
                    guard !trimmed.isEmpty else { return }
                    newMessage = ""
                    items.append(.init(timestamp: .now, text: trimmed))
                    withAnimation(.smooth) {
                        scrollPosition.scrollTo(id: items.last!.id, anchor: .bottom)
                    }
                })
            }.padding()
        }
    }
}

struct ItemView: View {
    let item: Item
    var body: some View {
        VStack(alignment: .leading) {
            Text(item.text)
            Text(item.timestamp.formatted())
        }.padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)))
    }
}

#Preview {
    ContentView()
}
Answered by DTS Engineer in 898213022

Hello @xmollv,

It appears SwiftUI prioritizes user gesture input over pragmatic scroll operations, like scrollTo.

I haven't found this in writing explicitly, but it seems consistent and might be expected, rather than a bug. You can still file an enhancement request through Feedback Assistant with information on how this design impacts you. Bug Reporting: How and Why? explains how you can do that

One idea to work around this is to store the target in state and execute scrollTo inside onScrollPhaseChange once the phase becomes .idle.

The HIG for Scroll views includes some relevant UX considerations for automatic scrolling, this might help give you some ideas as well.

 Travis

Hello @xmollv,

It appears SwiftUI prioritizes user gesture input over pragmatic scroll operations, like scrollTo.

I haven't found this in writing explicitly, but it seems consistent and might be expected, rather than a bug. You can still file an enhancement request through Feedback Assistant with information on how this design impacts you. Bug Reporting: How and Why? explains how you can do that

One idea to work around this is to store the target in state and execute scrollTo inside onScrollPhaseChange once the phase becomes .idle.

The HIG for Scroll views includes some relevant UX considerations for automatic scrolling, this might help give you some ideas as well.

 Travis

SwiftUI's `scrollTo(id:anchor:)` doesn't work if the ScrollView is scrolling
 
 
Q