Scrolling through long lists with ScrollView and LazyVstack

What is the correct way to implement scrolling in a looong list that uses ScrollView and LazyVstack

Imagine I have some api that returns a longs list of comments with replies

The basic usecase is to scroll to the bottom(to the last comment) Most of the time this works fine

But, imagine some of the comments have many replies like 35 or more (or even 300)

User expands replies for the first post, then presses scroll to bottom. The scrollbar reaches the bottom and I see the blank screen. Sometimes the scrollbar may jump for a while before lazyvstack finishes loading or until I manually scroll up a bit or all the way up and down

What should I do in this case? Is this the swiftui performance problem that has no cure?

Abstract example:

ScrollViewReader { proxy in
    ScrollView {
        LazyVStack {
            ForEach(comments) { comment in
                CommentView(comment: comment)
                    .id("comment-\(comment.id)")
            }
        }
    }
}

struct CommentView: View {
    let comment: Comment
    @State var isExpanded = false

    var body: some View {
        VStack {
            Text(comment.text)

            if isExpanded {
                RepliesView(replies: comment.replies) // 35-300+ replies
            }
        }
    }
}
...

scroll

proxy.scrollTo("comment-\(lastComment.id)", anchor: .bottom)
Answered by Engineer in 865317022

You could also consider having your replies be direct children of the LazyVStack. I think the issue you're running into here is that the lazy stack is only lazy for the views in the ForEach, which in your case would be CommentView. Try flattening your CommentView to display the comment text followed by the full list of replies, so that the replies can be inserted lazily as well. That might get you closer to what you're trying to achieve.

They could be various things going on here depending on your implementation.

So the first thing here is to profile your view to identify what coudl be causing the performance issues. Please review Optimize SwiftUI performance with Instruments. Another thing to consider is if you want to load all your data into memory including comments or fetch those as needed.

That said, stack views load their child views all at once which makes layout fast and reliable, because the system knows the size and shape of every subview as it loads them while Lazy stacks trade some degree of layout correctness for performance, because the system only calculates the geometry for subviews as they become visible.

Accepted Answer

You could also consider having your replies be direct children of the LazyVStack. I think the issue you're running into here is that the lazy stack is only lazy for the views in the ForEach, which in your case would be CommentView. Try flattening your CommentView to display the comment text followed by the full list of replies, so that the replies can be inserted lazily as well. That might get you closer to what you're trying to achieve.

Ok, the issue is also that I have a complex attributed textview that does a lot of processing to render a comment(replacing various tags, setting font, posts referencing etc) Should have mentioned that

I added view.updateIfNeeded and moved replies out as suggested by @StevenPeterson. Feels better now

+some animations on scroll

Previously instruments showed a few micro hangs. Couldn't see any issues. Will try to dedicate more time to get familiar with it.

func scrollToBottom() {
        guard !viewModel.processedPosts.isEmpty else { return }

        let last = viewModel.processedPosts.last!.post.num

        scrollProxy?.scrollTo(last, anchor: .bottom)

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.08) {
            withAnimation(.easeOut(duration: 0.2)) {
                scrollProxy?.scrollTo(last, anchor: .bottom)
            }
        }
    }

Just a quick follow up - the main problem was my post processor. Then I switched to swiftsoup and everything is much better now

Scrolling through long lists with ScrollView and LazyVstack
 
 
Q