Hi there, i'm currently trying to make a ChatView
with SwiftUI. I've been using "inverted" ScrollView, but now I see new defaultScrollAnchor
and scrollPosition
methods which can make solution more kind of native and less buggy.
So, here is the snippet of my implementation:
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageView(
message: message,
actions: viewModel.messageActions
)
}
}
}
.defaultScrollAnchor(.bottom)
At this point it works as expected most of the time. But once I have a message which content doesn't fit on the screen there is the problem.
When I enter the chat for the first time all is OK:
But next times I open the chat i see only this:
But the content is scrollable, once i scroll a little bit to the top, messages appear like they just were scrolled.
I think this is the problem with LazyVStack
, maybe it waits for the content top to be displayed to render item?
Here is the full code of my ChatView:
import Foundation
import SwiftUI
import SwiftData
import AlertKit
import Factory
struct ChatView: View {
@StateObject private var viewModel: ChatViewModel
@Environment(\.modelContext) private var modelContext
@Query private var messages: [ChatMessage]
@Query private var generatingMessages: [ChatMessage]
init(chat: Chat) {
_viewModel = StateObject(wrappedValue: Container.shared.chatViewModel(chat))
let chatId = chat.persistentModelID
_messages = Query(
filter: #Predicate {
$0.chat?.persistentModelID == chatId
},
sort: \.date,
animation: .spring
)
_generatingMessages = Query(
filter: #Predicate {
$0.generating && $0.chat?.persistentModelID == chatId
}
)
}
var body: some View {
VStack(spacing: 0) {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(messages) { message in
MessageView(
message: message,
actions: viewModel.messageActions
)
}
}
}
.defaultScrollAnchor(.bottom)
if !messages.isEmpty { Divider() }
HStack(alignment: .bottom) {
TextField("message", text: $viewModel.text, axis: .vertical)
.padding(.vertical, 7)
.padding(.horizontal, 12)
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 20))
.onSubmit {
sendMessage()
}
SendButton(enabled: generatingMessages.isEmpty) {
sendMessage()
}
.animation(.default, value: generatingMessages.count)
.padding(.vertical, 7)
}
.padding(.horizontal)
.padding(.vertical, 10)
}
.toastingErrors(with: viewModel)
.scrollDismissesKeyboard(.interactively)
.navigationTitle(viewModel.chat.title ?? "")
.navigationBarTitleDisplayMode(.inline)
}
private func sendMessage() {
if viewModel.text.isEmpty {
return
}
viewModel.startLoading()
}
}
So, is there any workaround for this issue? Or maybe I do something wrong?