diff --git a/yobble/Views/Chat/PrivateChatView.swift b/yobble/Views/Chat/PrivateChatView.swift index fe87025..71eea04 100644 --- a/yobble/Views/Chat/PrivateChatView.swift +++ b/yobble/Views/Chat/PrivateChatView.swift @@ -3,9 +3,11 @@ import SwiftUI struct PrivateChatView: View { let chat: PrivateChatListItem let currentUserId: String? + private let bottomAnchorId = "PrivateChatBottomAnchor" @StateObject private var viewModel: PrivateChatViewModel @State private var hasPositionedToBottom: Bool = false + @State private var scrollToBottomTrigger: UUID = .init() @State private var draftText: String = "" @State private var inputTab: ComposerTab = .chat @State private var isVideoPreferred: Bool = false @@ -22,15 +24,23 @@ struct PrivateChatView: View { ScrollViewReader { proxy in content .onChange(of: viewModel.messages.count) { _ in - guard !viewModel.isLoadingMore, - let lastId = viewModel.messages.last?.id else { return } + guard !viewModel.isLoadingMore else { return } + let targetId = viewModel.messages.last?.id ?? bottomAnchorId DispatchQueue.main.async { withAnimation(.easeInOut(duration: 0.2)) { - proxy.scrollTo(lastId, anchor: .bottom) + proxy.scrollTo(targetId, anchor: .bottom) } hasPositionedToBottom = true } } + .onChange(of: scrollToBottomTrigger) { _ in + let targetId = viewModel.messages.last?.id ?? bottomAnchorId + DispatchQueue.main.async { + withAnimation(.easeInOut(duration: 0.2)) { + proxy.scrollTo(targetId, anchor: .bottom) + } + } + } } .navigationTitle(title) .navigationBarTitleDisplayMode(.inline) @@ -85,6 +95,10 @@ struct PrivateChatView: View { !viewModel.messages.isEmpty { errorBanner(message: message) } + + Color.clear + .frame(height: 1) + .id(bottomAnchorId) } .padding(.vertical, 12) } @@ -324,6 +338,7 @@ struct PrivateChatView: View { guard !text.isEmpty else { return } draftText = "" + scrollToBottomTrigger = .init() viewModel.sendMessage(text: text) { success in if success { hasPositionedToBottom = true