SwiftUI ScrollView maintain position on new page load

577 views Asked by At

In my chat view, each time I load new page (items are added from top), the ScrollView jumps to top instead of maintaining scrollPosition.

enter image description here

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.

1

There are 1 answers

7
Mojtaba Hosseini On

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:

  • Initially, ScrollView is in the top position (lets say y: 0) and the Text("Oct 27") is in y: 0.
  • on next load, Text("Oct 27") goes below (lets say y: 500) and now Text("Oct 14") is in y: 0!
  • After the 2nd load, you are seeing 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.