Reputation: 979
In my chat view, each time I load new page (items are added from top), the ScrollView
jumps to top instead of maintaining scrollPosition
.
Here is my scroll view:
GeometryReader { geometryProxy in
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
if viewModel.isLoading {
LoadingFooter()
}
messagesView
.frame(minHeight: geometryProxy.size.height - loadingFooterHeight - bottomContentMargins, alignment: .bottom)
}
}
.scrollDismissesKeyboard(.interactively)
.defaultScrollAnchor(.bottom)
.scrollPosition(id: $scrolledId, anchor: .top)
.contentMargins(.bottom, bottomContentMargins, for: .scrollContent)
.onChange(of: scrolledId, scrollViewDidScroll)
}
And this is the messages view
@ViewBuilder var messagesView: some View {
LazyVStack(spacing: 0) {
ForEach(sectionedMessages) { section in
Section(header: sectionHeaderView(title: section.id)) {
ForEach(section, id: \.id) { message in
MessageView(message: message)
.padding(.horizontal, .padding16)
.padding(.bottom, .padding8)
.id(message.id)
}
}
}
.scrollTargetLayout()
}
}
Printing the scrolledId
after a page load, I can see it hasn't changed, but the ScrollView position does.
Upvotes: 7
Views: 1664
Reputation: 1768
I suppose I am a bit late but there is a simple solution in iOS18
@Binding var items: [MyItem]
@State private var position: ScrollPosition
= .init(idType: MyItem.ID.self)
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemView(item)
}
}
.scrollTargetLayout()
}
.scrollPosition($scrolledID)
The:
scrollPosition(id: $scrolledId, anchor: .top)
works but isn't reliable at all. Tried also mixing it with the
proxy.scrollTo(scrolledID)
but never getting a perfect result.
For more info here is the documentation link: ScrollPosition Apple Documentation
Upvotes: 0
Reputation: 119917
The scroll view does not jump and the position is persisted. It's the old content shifted below the new content!
In the short video you've provided:
ScrollView
is in the top position (lets say y: 0
) and the Text("Oct 27")
is in y: 0
.Text("Oct 27")
goes below (lets say y: 500
) and now Text("Oct 14")
is in y: 0
!Oct 13
at y: 0
.So you need to move the scroll view on where the old data goes after each load. So it seems like a consistent content size to the user some how.
Here is a pseudo code for this concept:
ScrollViewReader { scrollProxy in // 👈 Get the proxy
ScrollView {
,,,
Text("Oct 27")
.id("Title - Oct 27") // 👈 Give it a proper id
}
.onChange(of: messages) { oldValue, newValue in
if needed {
scrollProxy.scrollTo("Title - Oct 27") // 👈 Scroll to it after load only if needed
}
}
}
Note that you may want to have a more accurate position of the content before and after the load. But it will have some more calculations and out of the scope of this question.
Upvotes: 0