You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

消息界面未读消息计数及输入状态展示功能技术咨询

需求实现方案:未读消息计数 + 正在输入状态

一、未读消息计数功能实现

1. 调整消息存储结构

当前你直接将消息文本存在键值对中,需要给每条消息新增已读状态字段,把单文本存储改成包含多字段的字典结构:

// 替换原发送逻辑里的updateChildValues代码
let messageData = [
    "message": txtMsg.text!,
    "isRead": false,
    "senderUid": Auth.auth().currentUser!.uid
] as [String : Any]

let targetUid = dict.object(forKey: "userid") as! String
let currentUid = Auth.auth().currentUser!.uid
let database = Database.database().reference()

// 双向存储消息(保持双方聊天数据同步)
database.child("Chats").child(targetUid).child(currentUid).updateChildValues([str : messageData])
database.child("Chats").child(currentUid).child(targetUid).updateChildValues([str : messageData])

2. 统计未读消息数量

在聊天界面监听消息变化,统计对方发送的、未标记为已读的消息数量:

func fetchUnreadCount() {
    let currentUid = Auth.auth().currentUser!.uid
    let targetUid = dict.object(forKey: "userid") as! String
    let chatRef = Database.database().reference().child("Chats").child(currentUid).child(targetUid)
    
    chatRef.observe(.value) { snapshot in
        var unreadCount = 0
        for child in snapshot.children {
            guard let childSnapshot = child as? DataSnapshot,
                  let messageDict = childSnapshot.value as? [String:Any],
                  let isRead = messageDict["isRead"] as? Bool,
                  let senderUid = messageDict["senderUid"] as? String,
                  senderUid != currentUid else { continue }
            
            if !isRead {
                unreadCount += 1
            }
        }
        // 更新界面计数(比如导航栏badge或界面内的数字标签)
        self.navigationItem.badgeValue = unreadCount > 0 ? "\(unreadCount)" : nil
    }
}

3. 标记消息为已读

当用户进入聊天界面时,将对方发送的所有未读消息批量标记为已读:

func markMessagesAsRead() {
    let currentUid = Auth.auth().currentUser!.uid
    let targetUid = dict.object(forKey: "userid") as! String
    let chatRef = Database.database().reference().child("Chats").child(currentUid).child(targetUid)
    
    chatRef.observeSingleEvent(of: .value) { snapshot in
        var updates = [String:Any]()
        for child in snapshot.children {
            guard let childSnapshot = child as? DataSnapshot,
                  let messageDict = childSnapshot.value as? [String:Any],
                  let isRead = messageDict["isRead"] as? Bool,
                  let senderUid = messageDict["senderUid"] as? String,
                  senderUid != currentUid, !isRead else { continue }
            
            updates["\(childSnapshot.key)/isRead"] = true
        }
        if !updates.isEmpty {
            chatRef.updateChildValues(updates)
            // 同步更新对方侧的消息已读状态(保持数据一致性)
            Database.database().reference().child("Chats").child(targetUid).child(currentUid).updateChildValues(updates)
        }
    }
}

在聊天界面的viewWillAppear方法中调用markMessagesAsRead()和fetchUnreadCount()。

二、「正在输入」状态实现

1. 监听本地输入状态并同步到数据库

新增TypingStatus节点存储用户输入状态,监听输入框的编辑事件:

func setupTypingListener() {
    txtMsg.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}

@objc func textFieldDidChange() {
    let currentUid = Auth.auth().currentUser!.uid
    let targetUid = dict.object(forKey: "userid") as! String
    let typingRef = Database.database().reference().child("TypingStatus").child(targetUid).child(currentUid)
    
    if txtMsg.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
        // 标记为正在输入,10秒后自动清除状态(避免用户停留在输入界面未发送的情况)
        typingRef.setValue(true)
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
            typingRef.removeValue()
        }
    } else {
        // 停止输入,清除状态
        typingRef.removeValue()
    }
}

2. 监听对方输入状态并更新界面

在聊天界面监听对方的输入状态,动态显示/隐藏「正在输入」提示:

func listenForTypingStatus() {
    let currentUid = Auth.auth().currentUser!.uid
    let targetUid = dict.object(forKey: "userid") as! String
    let typingRef = Database.database().reference().child("TypingStatus").child(currentUid).child(targetUid)
    
    typingRef.observe(.value) { snapshot in
        if snapshot.exists() {
            // 显示「正在输入」提示(需提前在界面添加UILabel:typingStatusLabel)
            self.typingStatusLabel.isHidden = false
            self.typingStatusLabel.text = "正在输入"
        } else {
            self.typingStatusLabel.isHidden = true
        }
    }
}

在viewDidLoad中调用setupTypingListener()和listenForTypingStatus(),同时记得在viewWillDisappear中移除所有数据库监听,避免内存泄漏:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    let currentUid = Auth.auth().currentUser!.uid
    let targetUid = dict.object(forKey: "userid") as! String
    Database.database().reference().child("Chats").child(currentUid).child(targetUid).removeAllObservers()
    Database.database().reference().child("TypingStatus").child(currentUid).child(targetUid).removeAllObservers()
}

注意事项

  • 需配置正确的Firebase数据库规则,确保用户仅能访问自己的聊天数据和输入状态节点;
  • 处理用户退出登录或切换账号时的状态清理,避免无效数据残留。

内容的提问来源于stack exchange,提问作者Krunal Kathiriya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 00:42:07