SwiftUI - 滚动到聊天应用程序的最新消息
Posted
技术标签:
【中文标题】SwiftUI - 滚动到聊天应用程序的最新消息【英文标题】:SwiftUI - scrolling to latest message for chat applictaion 【发布时间】:2021-01-17 20:46:09 【问题描述】:我是这里的新手,最近才开始编码,所以这是我的第一篇文章/问题。
我目前正在用 SwiftUI 和 Firebase 编写一个聊天应用程序。
当我进入聊天室时,我似乎无法让聊天窗口自动滚动以显示最新消息。 (它总是固定在顶部)。根据我的搜索,我看到不同的帖子说我应该使用“scrollTo”和“Anchor to bottom”。但是我没有成功地让它工作。
我的用于从 firebase/firstore 后端获取消息的 ViewModel 如下。
注意:我的 Firestore 数据库中的“chatrooms”集合包含“messages”子集合import Foundation
import Firebase
struct Message: Codable, Identifiable
var id: String?
var content: String
var name: String
var sender: String
var sendAt: String
class MessagesViewModel: ObservableObject
@Published var messages = [Message]()
private let db = Firestore.firestore()
private let user = Auth.auth().currentUser
func sendMessage(messageContent: String, docId: String)
if (user != nil)
db.collection("chatrooms").document(docId).collection("messages")
.addDocument(data: ["sentAt": Date(), "displayName": user!.email, "content": messageContent, "sender": user!.uid])
func fetchData(docId: String)
if (user != nil)
db.collection("chatrooms").document(docId).collection("messages").order(by: "sentAt", descending: false).addSnapshotListener((snapshot, error) in
guard let documents = snapshot?.documents else
print("no documents")
return
self.messages = documents.map docSnapshot -> Message in
let data = docSnapshot.data()
let docId = docSnapshot.documentID
let content = data["content"] as? String ?? ""
let displayName = data["displayName"] as? String ?? ""
let sender = data["sender"] as? String ?? ""
let sendAt = data["sendAt"] as? String ?? ""
return Message(id: docId, content: content, name: displayName, sender: sender, sendAt: sendAt)
)
而且,这是我在聊天窗口上呈现消息的视图
import SwiftUI
import Firebase
import FBSDKLoginKit
struct Messages: View
let chatroom: Chatroom
@ObservedObject var viewModel = MessagesViewModel()
@ObservedObject var Session = SessionStore()
@State var messageField = ""
let userID = Auth.auth().currentUser?.uid
init(chatroom: Chatroom)
self.chatroom = chatroom
viewModel.fetchData(docId: chatroom.id)
var body: some View
VStack
ScrollView
ScrollViewReader scrollView in
LazyVStack
ForEach(viewModel.messages) message in
HStack
//places messages on left/right depending on who wrote it
if message.sender == userID
Spacer().padding(.leading)
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color(red: 229 / 255, green: 27 / 255, blue: 78 / 255))
.clipShape(Capsule())
else
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.border(Color.blue, width: 1)
.clipShape(Capsule())
Spacer().padding(.trailing)
HStack
TextField("Enter message...", text: $messageField)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action:
viewModel.sendMessage(messageContent: messageField, docId: chatroom.id)
, label:
Text("Send")
)
.navigationBarTitle(Text(chatroom.title), displayMode: .inline)
struct Messages_Previews: PreviewProvider
static var previews: some View
Messages(chatroom: Chatroom(id: "10101", title: "Hello!", joinCode: 10))
到目前为止我尝试过的几件事
.onChange(of: viewModel.messages, perform: value in scrollView.scrollTo(viewModel.messages.last!.id, anchor:.bottom)
.onAppear scrollView.scrollTo(viewModel.messages[viewModel.messages.endIndex - 1])
我在上面尝试过的两种解决方案都可以正常工作,但不幸的是,这两种解决方案都不适合我。 (还是聊天窗口需要手动滚动到底部)
非常感谢您阅读我的帖子。
更新
已修复 - 基于以下反馈。但是我需要 .onAppear 让它在我现在正在处理的加载时滚动到底部
ScrollView
ScrollViewReader scrollView in
LazyVStack
ForEach(viewModel.messages) message in
HStack
//places messages on left/right depending on who wrote it
if message.sender == userID
Spacer().padding(.leading)
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color(red: 229 / 255, green: 27 / 255, blue: 78 / 255))
.clipShape(Capsule())
else
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.clipShape(Capsule())
Spacer().padding(.trailing)
.id(message.id)
.onChange(of: viewModel.lastMessageID) id in
withAnimation
scrollView.scrollTo(id)
【问题讨论】:
【参考方案1】:为 msg 定义 id 而不是 msg.id,然后滚动到该 msg -> scrollTo(msg)
例子:
ScrollViewReader scrollView in
LazyVStack
ForEach(viewModel.messages) message in
HStack
//places messages on left/right depending on who wrote it
if message.sender == userID
Spacer().padding(.leading)
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color(red: 229 / 255, green: 27 / 255, blue: 78 / 255))
.clipShape(Capsule())
else
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.clipShape(Capsule())
Spacer().padding(.trailing)
.id(message)
.onChange(of: viewModel.messages.last) msg in
withAnimation
scrollView.scrollTo(msg)
【讨论】:
【参考方案2】:您需要提供id
以使视图滚动到以使滚动阅读器工作,例如
ScrollView
ScrollViewReader scrollView in
LazyVStack
ForEach(viewModel.messages) message in
HStack
// ... other code here
.id(message.id) // << here (or for Text inside)
另请参阅下一个工作示例https://***.com/a/60855853/12299030
【讨论】:
我尝试定义每条消息的 id,但仍然没有运气(在上面的帖子中更新)。你能再看看吗?【参考方案3】:这应该可以完成工作:
ScrollView
ScrollViewReader scrollView in
LazyVStack
ForEach(viewModel.messages) message in
HStack
//places messages on left/right depending on who wrote it
if message.sender == userID
Spacer().padding(.leading)
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color(red: 229 / 255, green: 27 / 255, blue: 78 / 255))
.clipShape(Capsule())
else
Text(message.content)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.clipShape(Capsule())
Spacer().padding(.trailing)
.id(message.id)
.onChange(of: viewModel.messages.last) msg in
withAnimation
scrollView.scrollTo(msg.id)
.onAppear
withAnimation
scrollView.scrollTo(viewModel.messages.last!.id)
我还强烈建议您使用 FirebaseFirestoreSwift 向您的模型添加 @DocumentID 并使其可编码。它会变成这样:
struct Message: Codable, Identifiable
var id: String?
var content: String
var name: String
var sender: String
var sendAt: String
guard let documents = snapshot?.documents else
print("no documents")
return
self.messages = documents.map docSnapshot -> Message in
let data = docSnapshot.data()
let docId = docSnapshot.documentID
let content = data["content"] as? String ?? ""
let displayName = data["displayName"] as? String ?? ""
let sender = data["sender"] as? String ?? ""
let sendAt = data["sendAt"] as? String ?? ""
return Message(id: docId, content: content, name: displayName, sender: sender, sendAt: sendAt)
到这里:
struct Message: Codable, Identifiable
@DocumentID var id: String? = UUID().uuidString
// or this depending on your preference -> @DocumentID var id: String?
var content: String
var name: String
var sender: String
var sendAt: String
guard let documents = snapshot?.documents else
print("no documents")
return
self.messages = documents.compactMap queryDocumentSnapshot -> Message? in
return try? queryDocumentSnapshot.data(as: Message.self)
来源:https://peterfriese.dev/posts/swiftui-firebase-codable/
【讨论】:
以上是关于SwiftUI - 滚动到聊天应用程序的最新消息的主要内容,如果未能解决你的问题,请参考以下文章
使用 MessageKit 构建的聊天视图永远不会滚动到带有图片的最后一条消息
出现键盘时滚动到 SwiftUI Scrollview 的最底部