79128716

Date: 2024-10-26 14:21:50
Score: 0.5
Natty:
Report link

Your example code is setting the scroll position by using .scrollPosition on the ScrollView, together with .scrollTargetLayout on the LazyVStack. The trouble is, .scrollPosition can only have one anchor. If the anchor is .top then it is difficult to set the position to the last entry at the bottom and the same goes for the first entry at the top when the anchor is .bottom. So:

Other notes:

Here is the updated example:

struct GoodChatView: View {
    @State private var messages: [Message] = []
    @State private var isLoading = true

    var body: some View {
        ScrollViewReader { scrollView in
            ScrollView {
                LazyVStack {
                    ForEach(messages.reversed(), id: \.id) { message in
                        ChatMessage(text: "\(message.text)")
                            .background {
                                GeometryReader { proxy in
                                    let minY = proxy.frame(in: .scrollView).minY
                                    let isReadyForLoad = abs(minY) <= 0.01 && message == messages.last
                                    Color.clear
                                        .onChange(of: isReadyForLoad) { oldVal, newVal in
                                            if newVal && !isLoading {
                                                isLoading = true
                                                Task { @MainActor in
                                                    await loadMoreData()
                                                    await Task.yield()
                                                    scrollView.scrollTo(message.id, anchor: .top)
                                                    await resetLoadingState()
                                                }
                                            }
                                        }
                                }
                            }
                            .onAppear {
                                if !isLoading && message == messages.first {
                                    isLoading = true
                                    Task {
                                        await loadNewData()

                                        // When new data is appended, the scroll position is
                                        // retained - no need to set it again

                                        await resetLoadingState()
                                    }
                                }
                            }
                    }
                }
            }
            .task { @MainActor in
                await loadNewData()
                if let firstMessageId = messages.first?.id {
                    try? await Task.sleep(for: .milliseconds(10))
                    scrollView.scrollTo(firstMessageId, anchor: .bottom)
                }
                await resetLoadingState()
            }
        }
    }

    @MainActor
    func loadMoreData() async {
        let lastId = messages.last?.id ?? 0
        print("old data > \(lastId)")
        var oldMessages: [Message] = []
        for i in lastId+1...lastId+20 {
            let message = Message(id: i, text: "\(i)")
            oldMessages.append(message)
        }
        messages += oldMessages
    }

    @MainActor
    func loadNewData() async {
        let firstId = messages.first?.id ?? 21
        print("new data < \(firstId)")
        var newMessages: [Message] = []
        for i in firstId-20...firstId-1 {
            let message = Message(id: i, text: "\(i)")
            newMessages.append(message)
        }
        messages.insert(contentsOf: newMessages, at: 0)
    }

    @MainActor
    private func resetLoadingState() async {
        try? await Task.sleep(for: .milliseconds(500))
        isLoading = false
    }
}

Animation

Reasons:
  • RegEx Blacklisted phrase (2): any suggestions
  • RegEx Blacklisted phrase (2): I'm a beginner
  • Long answer (-1):
  • Has code block (-0.5):
  • High reputation (-2):
Posted by: Benzy Neez