I've searched this this all over this forum and the web, and I keep seeing "solutions" that don't actually seem to solve my (simple, I think) case. I'm trying to build a scrolling "conversation" view (think iMessage) where as each new line is added at the bottom, the view scrolls to completely display it. Here's what I've got:
struct ConversationView: View {
@Environment(GameModel.self) private var gameModel
@State var shownSteps: Int = 0
var body: some View {
VStack {
HStack {
Spacer()
Button {
//...random button actions...
} label: {
Text("Skip Conversation")
}
}.padding(5)
ScrollViewReader { proxy in
List {
ForEach (gameModel.conversationPoints) {cp in
let ind = gameModel.conversationPoints.firstIndex(of: cp)!
if (ind <= shownSteps)
{
ConversationLineView(step: ind, shownSteps: $shownSteps).border(Color.blue)
.id(cp.id)
}
}
}.onChange(of: shownSteps)
{ a, b in proxy.scrollTo(gameModel.conversationPoints[shownSteps].id, anchor: .bottom)}
}
}
}
}
Basically "shownSteps" is how many entries there are visible at the moment (out of a pre-populated list of "conversation points"), and every time it changes, I want to scroll to that entry.
This looks to me identical to several "working" examples I've found online, and it's obviously close, because it works in some places, but not others:
macOS: Code works fine in Sequioa betas, but doesn't scroll (new messages just show up below the bottom of the scroll region) at all in Sonoma.
iPadOS: The opposite: Works in 17, doesn't work in 18. (My app doesn't run on phone, but I assume the same for iOS)
visionOS: Works in 2.0 betas, haven't checked 1.2.
Any ideas?
Please file a bug report using Feedback Assistant and post the Feedback number here.
As a workaround use a ScrollView
+ VStack
instead of aList
. For example:
struct ContentView: View {
@State private var position: Int? = nil
@State private var data: [Item] = []
var body: some View {
ScrollView {
ScrollViewReader { proxy in
VStack {
ForEach(data) { item in
Text("\(item.value)")
.frame(height: 50)
.id(item.id) // Use item.id as the identifier
}
}
.onChange(of: data) { _, _ in
// Scroll to the last element when data changes
if let lastItem = data.last {
withAnimation {
proxy.scrollTo(lastItem.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
.overlay(alignment: .bottomTrailing) {
Button("Add Item") {
// add new item to data
let newValue = (data.last?.value ?? 0) + 1
data.append(Item(value: newValue))
}
}
.task {
for i in 0..<100 {
data.append(Item(value: i))
}
}
}
}
struct Item: Identifiable, Hashable {
let id = UUID()
let value: Int
}