SwiftUI ScrollView scrollTo not consistently scrolling to latest message

I am implementing an AI chat application and aiming to achieve ChatGPT-like behavior. Specifically, when a new message is sent, the ScrollView should automatically scroll to the top to display the latest message.

I am currently using the scrollTo method for this purpose, but the behavior is inconsistent—sometimes it works as expected, and other times it does not. I’ve noticed that this issue has been reported in multiple places, which suggests it may be a known SwiftUI limitation.

I’d like to know:

Has this issue been fixed in recent SwiftUI versions, or does it still persist?

If it still exists, is there a reliable solution or workaround that anyone can recommend?

Answered by DTS Engineer in 874124022

You can’t scroll to an item added in the same update; by the time you call scrollTo, the system don’t know about the new item.

The right way to do this is to use onChange so that SwiftUI enqueues a new update once the collection has propagated through. Like so:

struct ContentView: View {
    @State private var items = Array(0 ..< 20)
    var body: some View {
        NavigationStack {
            ScrollViewReader { proxy in
                List {
                    ForEach(items, id: \.self) { item in
                        Text("Row \(item)")
                            .onTapGesture {
                                proxy.scrollTo(item, anchor: .bottom)
                            }
                    }
                }
                .toolbar {
                    Button {
                        let newItem = items.count
                        items.append(newItem)
                    } label: {
                        Image(systemName: "plus")
                    }
                }
                .onChange(of: items.count) {
                    proxy.scrollTo(items.count-1, anchor: .bottom)
                }
            }
        }
    }
}

Let me know if this helps, and if you have any further questions please share a sample code that reproduces the issue. That'll be helpful in getting more context.

Accepted Answer

You can’t scroll to an item added in the same update; by the time you call scrollTo, the system don’t know about the new item.

The right way to do this is to use onChange so that SwiftUI enqueues a new update once the collection has propagated through. Like so:

struct ContentView: View {
    @State private var items = Array(0 ..< 20)
    var body: some View {
        NavigationStack {
            ScrollViewReader { proxy in
                List {
                    ForEach(items, id: \.self) { item in
                        Text("Row \(item)")
                            .onTapGesture {
                                proxy.scrollTo(item, anchor: .bottom)
                            }
                    }
                }
                .toolbar {
                    Button {
                        let newItem = items.count
                        items.append(newItem)
                    } label: {
                        Image(systemName: "plus")
                    }
                }
                .onChange(of: items.count) {
                    proxy.scrollTo(items.count-1, anchor: .bottom)
                }
            }
        }
    }
}

Let me know if this helps, and if you have any further questions please share a sample code that reproduces the issue. That'll be helpful in getting more context.

Thanks for the quick response 🙌 I was actually doing the same thing already using onChange, and it works fine in simpler scenarios.

In my case, I’m building an AI chat app where responses are streamed and can get very long (for example, asking it to write a 1000-word essay). When these large streaming responses come in repeatedly, and the user scrolls somewhere in the middle of the chat and sends a new message, I noticed that sometimes the scroll doesn’t return to the expected position based on the message .id.

What helped a lot in my case was flipping the scroll view upside down so new messages naturally appear at the bottom. After doing this, the scrolling behavior became much more stable with long, continuously updating content.

.flippedUpsideDown()

func flippedUpsideDown() -> some View {
    self
        .rotationEffect(.radians(.pi))
        .scaleEffect(x: -1, y: 1, anchor: .center)
}

SwiftUI ScrollView scrollTo not consistently scrolling to latest message
 
 
Q