在我的MessagingViewController类上,每当我尝试从后端(Firestore)提取消息时,都会出现此错误:
我发现查询发生后collectionView不能更新,因此我在viewDidLoad()方法中添加了“ messagesCollectionView.reloadData()”。
没用。因此,之后,我在插入新消息的函数中添加了同一行:
private func insertNewMessage(_ message: Message) {
guard !messages.contains(message) else { return }
messages.append(message)
messages.sort()
messagesCollectionView.reloadData()
messagesCollectionView.performBatchUpdates({
messagesCollectionView.insertSections([messages.count - 1])
if messages.count >= 2 {
messagesCollectionView.reloadSections([messages.count - 2])
}
}, completion: { [weak self] _ in
if self?.isLastSectionVisible() == true {
self?.messagesCollectionView.scrollToBottom(animated: true)
}
})
}
仍然没有运气。如何正确加载查询中的消息并将其显示在collectionView上?是什么导致此错误?
其余代码如下:
final class MessageKitLogViewController: MessagesViewController, MessageInputBarDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private let user: User
private var currentUser: User
private var messages: [Message] = []
private let db = Firestore.firestore()
private var reference: CollectionReference?
private var partnerReference: CollectionReference?
private var messageListener: ListenerRegistration?
private let storage = Storage.storage().reference()
let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
init(user: User, currentUser: User) {
self.user = user
self.currentUser = currentUser
super.init(nibName: nil, bundle: nil)
title = user.name
}
deinit {
messageListener?.remove()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
guard let userID = Auth.auth().currentUser?.uid, let partnerID = self.user.id else { return }
reference = db.collection(["messages", userID, partnerID].joined(separator: "/"))
partnerReference = db.collection(["messages", partnerID, userID].joined(separator: "/"))
messageListener = reference?.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
return
}
snapshot.documentChanges.forEach { change in
self.handleDocumentChange(change)
}
}
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .never
} else {}
messageInputBar.delegate = self
messageInputBar.inputTextView.tintColor = Colors.indexedGray
messageInputBar.sendButton.tintColor = Colors.indexedGray
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
scrollsToBottomOnKeyboardBeginsEditing = true // default false
maintainPositionOnKeyboardFrameChanged = true // default false
// InputTextView
messageInputBar.inputTextView.layer.borderWidth = 0
messageInputBar.inputTextView.backgroundColor = .white
messageInputBar.inputTextView.font = UIFont.systemFont(ofSize: 16, weight: .regular)
messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 0.75)
// - LayoutMargin
messageInputBar.inputTextView.placeholderLabelInsets.left = 10
// Send Button
messageInputBar.sendButton.configure {
$0.title = "Send"
$0.setSize(CGSize(width: 55, height: 30), animated: true)
$0.backgroundColor = Colors.indexedGray
$0.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .regular)
$0.setTitleColor(.white, for: .normal)
$0.setTitleColor(.white, for: .highlighted)
$0.setTitleColor(UIColor(white: 0.9, alpha: 10), for: .disabled)
$0.layer.cornerRadius = 10.5
$0.layer.masksToBounds = true
}.onSelected {
$0.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
}.onDeselected {
$0.transform = CGAffineTransform.identity
}.onTextViewDidChange { (button, textView) in
button.messageInputBar?.setRightStackViewWidthConstant(to: textView.text.isEmpty ? 0:50, animated: true)
}
messageInputBar.setRightStackViewWidthConstant(to: 0, animated: false)
messagesCollectionView.reloadData()
}
@objc private func cameraButtonPressed() {
let picker = UIImagePickerController()
picker.delegate = self
if UIImagePickerController.isSourceTypeAvailable(.camera) {
picker.sourceType = .camera } else {
picker.sourceType = .photoLibrary
}
present(picker, animated: true, completion: nil)
}
private func save(_ message: Message) {
reference?.addDocument(data: message.representation) { error in
if let e = error {
print("Error sending message: \(e.localizedDescription)")
return
}
self.partnerReference?.addDocument(data: message.representation) { error in
if let e = error {
print("Error sending message: \(e.localizedDescription)")
return
}
}
self.messagesCollectionView.scrollToBottom()
}
}
private func insertNewMessage(_ message: Message) {
guard !messages.contains(message) else { return }
messages.append(message)
messages.sort()
messagesCollectionView.reloadData()
messagesCollectionView.performBatchUpdates({
messagesCollectionView.insertSections([messages.count - 1])
if messages.count >= 2 {
messagesCollectionView.reloadSections([messages.count - 2])
}
}, completion: { [weak self] _ in
if self?.isLastSectionVisible() == true {
self?.messagesCollectionView.scrollToBottom(animated: true)
}
})
}
private func handleDocumentChange(_ change: DocumentChange) {
guard var message = Message(document: change.document) else {
print("return Message")
return
}
switch change.type {
case .added:
print("add Message")
insertNewMessage(message)
default:
break
}
}
private func downloadImage(at url: URL, completion: @escaping (UIImage?) -> Void) {
let ref = Storage.storage().reference(forURL: url.absoluteString)
let megaByte = Int64(1 * 1024 * 1024)
ref.getData(maxSize: megaByte) { data, error in
guard let imageData = data else {
completion(nil)
return
}
completion(UIImage(data: imageData))
}
}
func isLastSectionVisible() -> Bool {
guard !messages.isEmpty else { return false }
let lastIndexPath = IndexPath(item: 0, section: messages.count - 1)
return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
}
func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
for component in inputBar.inputTextView.components {
if let str = component as? String {
let message = Message(user: self.currentUser, content: text)
save(message)
} else if let img = component as? UIImage {
var message = Message(user: self.currentUser, image: img)
save(message)
}
}
inputBar.inputTextView.text = String()
messagesCollectionView.scrollToBottom(animated: true)
}
}
扩展MessageKitLogViewController:MessagesDataSource { func numberOfSections(在messagesCollectionView中:MessagesCollectionView)-> Int { 返回messages.count }
func currentSender() -> Sender {
return Sender(id: currentUser.id!, displayName: currentUser.name!)
}
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int {
return messages.count
}
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
return messages[indexPath.section]
}
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
if indexPath.section % 3 == 0 {
return NSAttributedString(string: MessageKitDateFormatter.shared.string(from: message.sentDate), attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: UIColor.darkGray])
}
return nil
}
func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let name = message.sender.displayName
return NSAttributedString(string: name, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
}
func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let dateString = formatter.string(from: message.sentDate)
return NSAttributedString(string: dateString, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)])
}
}
扩展MessageKitLogViewController:MessagesLayoutDelegate {
func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
return .zero
}
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
return CGSize(width: 0, height: 8)
}
func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
return 0
}
}
扩展MessageKitLogViewController:MessagesDisplayDelegate {
func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
return isFromCurrentSender(message: message) ? Colors.indexedPrimary : .lightGray
}
func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool {
return false
}
func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft
return .bubbleTail(corner, .curved)
}
}