According to Apple's documentation on ScrollViewReader
, it should wrap the scroll view, instead of being nested in.
Example from Apple docs:
@Namespace var topID
@Namespace var bottomID
var body: some View {
ScrollViewReader { proxy in
ScrollView {
Button("Scroll to Bottom") {
withAnimation {
proxy.scrollTo(bottomID)
}
}
.id(topID)
VStack(spacing: 0) {
ForEach(0..<100) { i in
color(fraction: Double(i) / 100)
.frame(height: 32)
}
}
Button("Top") {
withAnimation {
proxy.scrollTo(topID)
}
}
.id(bottomID)
}
}
}
func color(fraction: Double) -> Color {
Color(red: fraction, green: 1 - fraction, blue: 0.5)
}
This other article also shows how to use ScrollViewReader
to wrap a List
of elements, which can be also handy. Code example from the article:
ScrollViewReader { scrollView in
List {
ForEach(photos.indices) { index in
Image(photos[index])
.resizable()
.scaledToFill()
.cornerRadius(25)
.id(index)
}
}
.
.
.
}
For me, using value.scrollTo(entries.count - 1)
does not work. Furthermore, using value.scrollTo(entries.last?.id)
didn't work either until I used the
.id(...)
view modifier (maybe because my entries do not conform to Identifiable
).
Using ForEach
, here is the code I'm working on:
ScrollViewReader { value in
ScrollView {
ForEach(state.messages, id: \.messageId) { message in
MessageView(message: message)
.id(message.messageId)
}
}
.onAppear {
value.scrollTo(state.messages.last?.messageId)
}
.onChange(of: state.messages.count) { _ in
value.scrollTo(state.messages.last?.messageId)
}
}
One important consideration when using onAppear
is to use that modifier on the ScrollView
/List
and not in the ForEach
. Doing so in the ForEach
will cause it to trigger as elements on the list appear when manually scrolling through it.