I can not work out why the LazyVStack will not snap again typically after dismissing the keyboard. There may be one factor I understood that’s when measurement of the views contained in the LazyVStack are identical there will not be any points however when measurement varies this problem arises.
Lazy is in background yellow and scrollview is in inexperienced. Simply put it like that to point out my problem clearly.
struct MessagesView: View {
@State non-public var messages: [ChatMessage] = MockChatMessages().loadAllMessages()
@State non-public var inputText: String = ""
@Binding var showChat: Bool
@State non-public var scrollToID: Int? // Used for iOS 17 auto-scroll
var physique: some View {
VStack(spacing: 0) {
HeaderView()
MessagesList(messages: messages, scrollToID: $scrollToID)
InputBar(inputText: $inputText, onSend: sendMessage)
}
.background(Shade.blue.opacity(0.3))
.ignoresSafeArea(edges: .prime)
.onAppear {
scrollToID = messages.final?.id
}
.onChange(of: messages.depend) { _ in
scrollToID = messages.final?.id
}
}
}
// MARK: - Header
struct HeaderView: View {
var physique: some View {
if #obtainable(iOS 17.0, *) {
Textual content("Chat")
.body(width: UIScreen.important.bounds.width, peak: 70)
.padding(.prime, 20)
.safeAreaPadding(.prime)
.background(Shade.pink.opacity(0.5))
.clipShape(Rectangle())
} else {
Textual content("Chat")
.body(peak: 70)
.background(Shade.pink.opacity(0.5))
.clipShape(Rectangle())
.padding(.prime, 20)
} }
}
// MARK: - Messages Checklist
struct MessagesList: View {
var messages: [ChatMessage]
@Binding var scrollToID: Int?
var physique: some View {
if #obtainable(iOS 17.0, *) {
ScrollView {
LazyVStack(spacing: 14) {
ForEach(messages, id: .id) { msg in
MessageBubble(message: msg)
}
}
.padding(.vertical)
.background(Shade.yellow.opacity(0.5))
}
.background(Shade.inexperienced.opacity(0.5))
.scrollIndicators(.hidden)
.scrollPosition(id: $scrollToID, anchor: .backside)
} else {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 14) {
ForEach(messages, id: .id) { msg in
MessageBubble(message: msg)
.id(msg.id)
}
}
.padding(.vertical)
}
.onChange(of: scrollToID) { id in
if let id = id {
withAnimation {
proxy.scrollTo(id, anchor: .backside)
}
}
}
}
}
}
}
// MARK: - Enter Bar
struct InputBar: View {
@Binding var inputText: String
var onSend: () -> Void
var physique: some View {
HStack {
TextField("Kind your message...", textual content: $inputText)
.padding(12)
.background(Shade.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
Button(motion: onSend) {
Textual content("Ship")
.foregroundColor(.white)
.padding(.vertical, 10)
.padding(.horizontal, 16)
.background(Shade.blue)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
.padding(.horizontal)
.padding(.backside, 12)
.background(Shade.grey.opacity(0.15))
}
}
// MARK: - Single Message Bubble
struct MessageBubble: View {
var message: ChatMessage
var isRight: Bool { message.path == .proper }
var physique: some View {
HStack {
if isRight { Spacer() }
Textual content(message.message)
.foregroundColor(isRight ? .white : .black)
.padding(.vertical, 10)
.padding(.horizontal, 12)
.background(isRight ? Shade.black : Shade.white)
.clipShape(RoundedRectangle(cornerRadius: 14))
.body(maxWidth: UIScreen.important.bounds.width * 0.7, alignment: isRight ? .trailing : .main)
if !isRight { Spacer() }
}
.padding(.horizontal, 12)
}
}
// MARK: - Add Message Perform
extension MessagesView {
func sendMessage() {
guard !inputText.isEmpty else { return }
let nextID = (messages.final?.id ?? 0) + 1
let msg = ChatMessage(
id: nextID,
path: .proper,
message: inputText
)
messages.append(msg)
inputText = ""
scrollToID = msg.id
}
}


