0

I am try to implement a chat feature, I am implementing it with a lazyVStack inside a scrollView where the most current message is at the bottom and the user scrolls up to get older messages (standard chat behavior). Everything works as expected, however when the I scroll to the top and fetch more messages I get a jarring affect where all new messages are displayed and I then need to scroll back down to the last message before the fetch occurred. It is a poor UX experience, everything I see online says you just need to scroll back which I don't understand because I don't see this behavior in any apps I use.

When messages are fetched I am inserting them into my array at index 0. If I reverse this were I scroll down to see old messages (and append them as they are fetched) everything works smoothly.

My desired behavior would be for my last message to be pushed slightly down on the load of more messages and the user then is able to continue scrolling up. Similar to an iMessage UX. Any links to example code would would be greatly appreciated.

I have tried different animations both inserting new data and on the scroll, I have adjusted how many messages are fetched. Some combinations create a better experience than others but nothing is great.

3
  • "you can't" :) :) Commented Jun 20, 2024 at 16:29
  • Please provide enough code so others can better understand or reproduce the problem. Commented Jun 20, 2024 at 19:39
  • this solves the issue swiftwithvincent.com/blog/… Commented Sep 18 at 14:07

1 Answer 1

4

I was able to find a solution for iOS 17 here: forums.developer.apple.com/forums/thread/731271

Specifically I needed to use .scrollTargetLayout() and .scrollPosition(id: $dataID).

For posterity, in case the solution page is removed, this is the code example I followed:

struct ContentView: View {
    @State var data: [String] = (0 ..< 25).map { String($0) }
    @State var dataID: String?

    var body: some View {
        ScrollView {
            VStack {
                Text("Header")

                LazyVStack {
                    ForEach(data, id: \.self) { item in
                        Color.red
                            .frame(width: 100, height: 100)
                            .overlay {
                                Text("\(item)")
                                    .padding()
                                    .background()
                            }
                    }
                }
                .scrollTargetLayout()
            }
        }
        .scrollPosition(id: $dataID)
        .safeAreaInset(edge: .bottom) {
            Text("\(Text("Scrolled").bold()) \(dataIDText)")
            Spacer()
            Button {
                dataID = data.first
            } label: {
                Label("Top", systemImage: "arrow.up")
            }
            Button {
                dataID = data.last
            } label: {
                Label("Bottom", systemImage: "arrow.down")
            }
            Menu {
                Button("Prepend") {
                    let next = String(data.count)
                    data.insert(next, at: 0)
                }
                Button("Append") {
                    let next = String(data.count)
                    data.append(next)
                }
                Button("Remove First") {
                    data.removeFirst()
                }
                Button("Remove Last") {
                    data.removeLast()
                }
            } label: {
                Label("More", systemImage: "ellipsis.circle")
            }
        }
    }

    var dataIDText: String {
        dataID.map(String.init(describing:)) ?? "None"
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

but what about iOS 16?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.