Firebase 教程 —— 一个实时聊天室

时间:2022-12-01 23:18:16

现在主流的 App 都开始支持聊天功能了——你的 App 是不是也该支持一下?

但是,制作一个聊天工具确实不是一件简单的任务。我们不但缺乏现成的专门针对聊天的 UIKit 组件,还需要一个服务器来负责处理用户间的消息及对话。

幸运的是,我们可以使用一个优秀的框架:Firebase。它能为我们同步实时数据而无需编写任何服务端代码,同时还提供一个 JSQMessagesViewController 用于显示消息,这个 UI 可以和本地的消息应用相媲美。

在这个 Firebase 教程中,我们会创建一个可以进行匿名聊天的 App 叫做 ChatChat,如下图所示:

Firebase 教程 —— 一个实时聊天室

最终你将学习到:

  1. 用 CocoaPods 安装 Firebase SDK 和 JSQMessagesViewController。
  2. 用 Firebase 数据库来同步实时数据。
  3. 让 Firebase 支持匿名登录。
  4. 用 JSQMessagesViewController 实现 UI。
  5. 当用户进行输入时,进行提示。

好了,接下来就开始吧!

开始

在开始本教程之前,请下载开始项目,目前,它只完成了一个假的登录界面。

我们可以用 CocoaPods 安装 Firebase SDK 和 JSQMessagesViewController。如果你对 CocoaPods 不熟,可以参考我们的CocoaPods Swift 教程

打开终端,进入项目文件夹路径。在项目根路径下,新建一个 Podfile 文件。在文件中加入对 Firebase SDK 和 JSQMessagesViewController 的依赖,如下所示:

platform :ios, "9.0"
use_frameworks!
target 'ChatChat' do
pod 'Firebase'
pod 'JSQMessagesViewController'
end

保存 Podfile 文件,然后用以下命令安装依赖:

pod install

译者注:由于众所周知的原因,CocoaPods 对于国内用户来说并不友好,经常出现各种无法 pod install 的情况。如果是这样,你必须手动安装这两个库了。关于 Firebase 的手动安装,请看这里。关于 JSQMessagesViewController 的手动安装,你需要从 github 下载 JSQMessagesViewController 和 JSQSystemSoundPlayer 这两个库的源文件然后添加到项目里,并改正 JSQSystemSoundPlayer+JSQMessages.m 中的两个错误即可,然后在桥接头文件中导入相应的 .h 文件(包括并不限于):

#import "JSQMessage.h"
#import "JSQMessagesBubbleImage.h"
#import "JSQMessagesViewController.h"
#import "JSQMessagesBubbleImageFactory.h"
#import "UIColor+JSQMessages.h"
#import "JSQMessageAvatarImageDataSource.h"
#import "JSQSystemSoundPlayer+JSQMessages.h"

一旦安装好,打开新生成的 ChatChat.xcworkspace 文件,编译运行程序,你将看到:

Firebase 教程 —— 一个实时聊天室

注意:在接下来的教程中,你每次编译和运行都会看到这个界面。点击“匿名登录”又会切换到另一个界面。目前点击按钮没有什么用处,但随后我们就会实现它。

如果你第一次接触 Firebase,你需要创建一个账号。不用担心—— 它非常简单,而且完全是免费的,不需要信用卡。

注意:关于如何注册 Firebase 的完整步骤,请看我们的Firebase 入门教程

注册 Firebase 账号

进入 Firebase 注册页面,创建一个账号,然后创建一个 Firebase App。就本教程而言,你需要使用实时数据库和身份认证服务。

开启匿名认证

Firebase 允许用户通过 email 地址或社交账号进行登录,但也提供匿名登录功能,后者会给每个用户分配一个唯一的 ID 但不需要用户的输入任何个人信息。

匿名认证就好比说:“我不知道你是谁,我只知道你是一个人。”。对于访问账户或者使用用户来说,这是非常方便的。这对于本教程来说非常适合,因为 ChatChat 中所有用户都是匿名的。

要开启匿名认证,你需要打开 Firebase App Dashboard,选择 Login & Auth 标签,点击 Anonymous,然后勾上 Enable Anonymous User Authentication 选项:

Firebase 教程 —— 一个实时聊天室

这样,你就开启了超级隐身模式,也就是匿名认证——很爽吧:]
Firebase 教程 —— 一个实时聊天室

登录

打开 LoginViewController.swift,加入:

import Firebase

要登入聊天室,需要连接到 Firebase 数据库。在 LoginViewController.swift 中加入:

class LoginViewController: UIViewController {

// MARK: Properties
var ref: Firebase! // 1

override func viewDidLoad() {
super.viewDidLoad()
ref = Firebase(url: "https://<my-firebase-app>.firebaseio.com") // 2
}
}

上面的代码是什么意思?

  1. 首先,定义一个属性,存放 Firebase database 的引用。
  2. 然后,用你的 Firebase App URL 创建一个 Firebase 数据库连接并赋给这个属性。

如果你不知道你的 Firebase App URL 是什么,你看一下 Firebase App Dashboard :
Firebase 教程 —— 一个实时聊天室

要登录一个用户,可以在数据库引用对象上调用 authAnonymouslyWithCompletionBlock(_:)。

在 loginDidTouch(_:) 方法中添加代码:

@IBAction func loginDidTouch(sender: AnyObject) {
ref.authAnonymouslyWithCompletionBlock { (error, authData) in // 1
if error != nil { print(error.description); return } // 2
self.performSegueWithIdentifier("LoginToChat", sender: nil) // 3
}
}

在这个方法中,完成了如下工作:

  1. 调用 ref 的 authAnonyouslyWithCompletionBlock(_:) 方法,以匿名方式登录一个用户。
  2. 检查是否认证失败。
  3. 在闭包中,调用 segue 跳转到 ChatViewController。

你可能会觉得奇怪:为什么代码中会忽略了 authData 对象。在这个对象中包含了用户的唯一标识,但你可以暂时把它扔到一边。因为当用户登录成功后,我们还可以通过 Firebase 数据库引用的 authData 属性来访问 authData 对象,例如:

// 登录成功后,打印当前登录用户的信息
print(ref.authData)

创建聊天界面

JSQMessagesViewController 是一个 UICollectionViewController 的封装,为聊天进行了专门的定制。

本教程将主要介绍 5 个步骤:

  1. 创建消息数据
  2. 创建带背景色的消息气泡
  3. 删除头像
  4. 改变 UICollectionViewCell 的文字颜色
  5. 提示用户正在输入

几乎每个步骤都需要覆盖一些方法。JSQMessagesViewController 使用了JSQMessagesCollectionViewDataSource 协议,因此我们需要覆盖协议的默认实现就可以了。

注意:关于 JSQMessagesCollectionViewDataSource 的更多内容,请参考这里

打开 ChatViewController.swift 导入 Firebase 和 JSQMessagesViewController :

import Firebase
import JSQMessagesViewController

将父类从 UIViewController 类修改 JSQMessagesViewController 类:

class ChatViewController: JSQMessagesViewController {

现在 ChatViewController 继承了 JSQMessagesViewController,我们可以设置 senderId 和 senderDisplayName 的初始值了,这样 App 才能唯一识别消息的发送者——否则它无从知道发送者是谁。

在 LoginViewController 中,我们用 ref.authData 将用户信息传递给 ChatViewController(通过 prepareForSegue 方法)。

在 LoginViewController 中添加方法:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
let navVc = segue.destinationViewController as! UINavigationController // 1
let chatVc = navVc.viewControllers.first as! ChatViewController // 2
chatVc.senderId = ref.authData.uid // 3
chatVc.senderDisplayName = "" // 4
}

在上面的代码中:

  1. 获取 segue 的目标 View Controller 并转换成 UINavigationController。
  2. 将 Navigation Controller 的第一个 View Controller 转换成 ChatViewController。
  3. 将本地用户的 ID 赋给 chatVc.senderId,这是 JSQMessagesViewController 用于处理消息的客户端 ID。
  4. 将 chatVc.senderDisplayName 设为空字符串,因为我们的聊天室是匿名登录的。

注意每个 App 会话中,我们只会有一个匿名的会话。每次重启 App 之后,你都会获得一个新的、唯一的匿名用户。如果你重启模拟器,你会看到另外一个用户 ID。
运行程序,检查你的 App 是否运行在超级隐身模式:

Firebase 教程 —— 一个实时聊天室

通过简单地继承下 JSQMessagesViewController,你就获得了一个完整的聊天 UI。太爽了!

Firebase 教程 —— 一个实时聊天室

创建数据源和委托

现在,我们有了一个聊天 UI,你可能很想在上面显示点什么了。但首先,你需要注意几件事情。

要显示聊天消息,我们需要一个数据源,即一个实现了 JSMessageData 协议的对象并实现一些委托方法。我们可以自己定义一个类来实现 JSQMessageData 协议,也可以使用现成的 JSQMessage 类。

在 ChatViewController 头部,定义一个属性:

// MARK: Properties
var messages = [JSQMessage]()

messages 属性是一个数组,存储了多个 JSQMessage 实例。

在 ChatViewController 中,实现 2 个委托方法:

override func collectionView(collectionView: JSQMessagesCollectionView!, 
messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}

override func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return messages.count
}

这两个委托方法可能并不陌生。第一个方法和 collectionView(_:cellForItemAtIndexPath:) 方法一样,只不过返回类型变成了 JSQMessageData 而已。第二个则和 collectionView(_:numberOfItemsInSection:) 方法完全一样。

还有几个必须实现的委托方法,用于提供消息数据、气泡图片以及头像。提供消息数据的方法已经实现了,接下来就是提供气泡和头像的方法。

气泡颜色

在 Collection View 中,消息文本显示在一个简单的图片背景之上。有两种类型的消息:收到的消息和发出的消息。发出的消息靠右侧显示而收到的消息则靠左显示。

在 ChatViewController 中,添加两个属性:

var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!

然后添加方法:

private func setupBubbles() {
let factory = JSQMessagesBubbleImageFactory()
outgoingBubbleImageView = factory.outgoingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleBlueColor())
incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleLightGrayColor())
}

JSQMessagesBubbleImageFactory 有创建聊天气泡的方法。在 JSQMessagesViewController 中有一个 Category,允许我们使用原生消息 App 中消息气泡所使用的颜色。

通过 bubbleImageFactory.outgoingMessagesBubbleImageWithColor() 和 bubbleImageFactory.incomingMessagesBubbleImageWithColor() 方法,我们可以创建出接收消息和发出消息的气泡图片。

然后,在 viewDidLoad() 方法中调用这个 setupBubbles() 方法:

override func viewDidLoad() {
super.viewDidLoad()
title = "ChatChat"
setupBubbles()
}

这样,我们就创建了接收消息和发出消息的气泡图片了!接下来,我们必须实现用于消息气泡的委托方法。

设置气泡图片

要为每条消息设置颜色气泡,我们需要覆盖 JSQMessagesCollectionViewDataSource 协议中的一个方法。

collectionView(_:messageBubbleImageDataForItemAtIndexPath:) 方法会要求我们为 CollectionView 中的每条消息数据提供一个与之相对应的 JSQMessageBubbleImageDataSource。这个方法正是我们设置气泡图片的好时机。

在 ChatViewController 添加方法:

override func collectionView(collectionView: JSQMessagesCollectionView!, 
messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}

逐行分析上面的代码:

  1. 根据 NSIndexPath 检索出对应的消息数据。
  2. 判断这条消息是否是本客户端所发出的,如果是,返回“发出消息”的 Image View。
  3. 如果不是,则返回“接收消息”的 ImageView。

在运行程序之前的最后一个步骤,是删除头像以及头像删除后留下的空白。

在 ChatViewController 中加入方法:

override func collectionView(collectionView: JSQMessagesCollectionView!, 
avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}

然后,在 viewDidLoad() 加入代码:

// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero

JSQMessagesViewController 支持头像显示,但我们用不到(或者不想),因为我们的 App 是一个匿名的聊天室。要删除头像显示,只需要在询问每条消息的头像时返回一个 nil 并将头像的 size 指定为 CGSizeZero,即“大小为 0”。

运行程序,当你登入聊天室后,会看到一个空白的聊天界面:

Firebase 教程 —— 一个实时聊天室

接下来开始对话并发送几条消息!

发送消息

在 ChatViewController 中增加方法:

func addMessage(id: String, text: String) {
let message = JSQMessage(senderId: id, displayName: "", text: text)
messages.append(message)
}

这个工具方法用于创建一条新的 displayName 为空的 JSQMessage,然后将它添加到数据源中。

在 viewDidAppear() 方法中硬编码几条消息以便我们能真正看到它们:

override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// messages from someone else
addMessage("foo", text: "Hey person!")
// messages sent from local sender
addMessage(senderId, text: "Yo!")
addMessage(senderId, text: "I like turtles!")
// animates the receiving of a new message on the view
finishReceivingMessage()
}

运行程序,你会看到会话窗口中显示了几条聊天消息:

Firebase 教程 —— 一个实时聊天室

呃,接收消息中的文字也太不显眼了。最好将它设置成黑色。

消息气泡中的文字

正如你所见,JSQMessagesViewController 中几乎每样东西都和一个委托方法有关。要设置文字颜色,我们可以使用经典的 collectionView(_:cellForItemAtIndexPath:) 方法。

在 ChatViewController 中加入一个方法:

override func collectionView(collectionView: UICollectionView, 
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath)
as! JSQMessagesCollectionViewCell

let message = messages[indexPath.item]

if message.senderId == senderId {
cell.textView!.textColor = UIColor.whiteColor()
} else {
cell.textView!.textColor = UIColor.blackColor()
}

return cell
}

如果消息是本客户端用户所发,则文字颜色为白色,否则文字颜色为黑色。

运行程序,接收消息的文字变成黑色的了:

Firebase 教程 —— 一个实时聊天室

哇——看起来养眼多了!是时候让它真正使用 Firebase 了。

Firebase 数据结构

在开始让数据实时同步之前,先花点时间来看看数据结构。

Firebase 数据库是 NoSQL 数据库,也就是说,在 Firebase 数据库中的每个对象都是 JSON 对象,这个 JSON 对象的每一个 key 都可以通过不同的 URL 来访问。

举一个例子,你的数据很可能是由这样一个 JSON 构成:

{
// https://<my-firebase-app>.firebaseio.com/messages
"messages": {
"1": { // https://<my-firebase-app>.firebaseio.com/messages/1
"text": "Hey person!", // https://<my-firebase-app>.firebaseio.com/messages/1/text
"senderId": "foo" // https://<my-firebase-app>.firebaseio.com/messages/1/senderId
},
"2": {
"text": "Yo!",
"senderId": "bar"
},
"2": {
"text": "Yo!",
"senderId": "bar"
},
}
}

Firebase 数据库支持“不规范的”数据结构,因此在每个 message 中都包含 senderId 是可以的。“不规范”的数据结构会导致一些数据冗余,但优点是检索数据的速度更快。权衡下来——我们还是可以接受的。

创建 Firebase 引用

在 ChatViewController.swift 中增加属性:

let rootRef = Firebase(url: "https://<my-firebase-app>.firebaseio.com/")
var messageRef: Firebase!

译者注:将 \ 替换成你自己的 Firebase App ID。

在 viewDidLoad() 方法中,初始化 messageRef:

override func viewDidLoad() {
super.viewDidLoad()
title = "ChatChat"
setupBubbles()
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
messageRef = rootRef.childByAppendingPath("messages")
}

我们创建了一个 rootRef 对象用于连接 Firebase 数据库。然后用 childByAppendingPath() 方法创建了一个 messageRef 对象,这个方法可用于创建下级引用。

不要奇怪,创建另一个引用并不意味着就需要创建新的连接。所有的引用其实可以共享同一个 Firebase 数据库连接。

发送消息

你可能迫不及待地想点击“Send”按钮了,如果你这样做,你会让 App 崩溃。现在,你已经连上了 Firebase 数据库,你可以真正地去发送几条消息了。

首先,删除 ChatViewController 中 viewDidAppear(_:) 方法中的测试消息。

然后,覆盖下面的这个方法。这个方法允许发送按钮将一条消息保存到 Firebase 数据库。

override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, 
senderDisplayName: String!, date: NSDate!) {

let itemRef = messageRef.childByAutoId() // 1
let messageItem = [ // 2
"text": text,
"senderId": senderId
]
itemRef.setValue(messageItem) // 3

// 4
JSQSystemSoundPlayer.jsq_playMessageSentSound()

// 5
finishSendingMessage()
}

这个方法负责:

  1. 通过 childByAutoId(),我们获得一个子对象引用,该对象有一个自动创建的唯一 key。
  2. 用一个字典来保存消息。一个 [String:AnyObject] 足以表示一个 JSON 对象。
  3. 将字典保存到新的子引用中。
  4. 播放一个经典的代表“消息已发送”的声音。
  5. 完成“发送”动作,将输入栏置空。

运行程序,打开你的 Firebase App Dashboard,点击 Data 栏。在 App 中发送一条消息,你立即会在 Dashboard 中看到这条消息显示。

Firebase 教程 —— 一个实时聊天室

成功了!你已经很“专业”地将消息保存到了 Firebase 数据库。这个消息还没有显示到 iPhone 上,但我们接下来就会这样做。

与 Firebase 保持实时同步

每当我们改变 Firebase 数据库中的数据,数据库就会将修改 push 给每个已经连接的 App 上。Firebase 的数据同步机制分为三个部分:URL、事件和快照。

例如,你可以用这种方式来监听新的消息:

let ref = Firebase(url: "https://<my-firebase-app>.firebaseio.com/messages") // 1
ref.observeEventType(.ChildAdded) { (snapshot: FDataSnapshot!) in { // 2
print(snapshot.value) // 3
}

这段代码主要是:

  1. 通过 Firebase App URL,我们创建了一个 Firebase 数据库引用。我们指定的这个 URL 指向了我们想读取的数据。
  2. 用 FEventType.ChildAdded 参数调用 observeEventType(_:FEventType:) 方法。在每当位于该 URL 的对象添加了新的子对象时都会触发一次 child-added 事件。
  3. 闭包中会传入一个 FDataSnapshot 对象,这个对象中会包含有相应的数据以及一些有用的方法。

同步数据源

看到了吧,和 Firebase 保持数据同步是非常简单的,接下来是和数据源进行对接。

在 ChatViewController 中增加一个方法:

private func observeMessages() {
// 1
let messagesQuery = messageRef.queryLimitedToLast(25)
// 2
messagesQuery.observeEventType(.ChildAdded) { (snapshot: FDataSnapshot!) in
// 3
let id = snapshot.value["senderId"] as! String
let text = snapshot.value["text"] as! String

// 4
self.addMessage(id, text: text)

// 5
self.finishReceivingMessage()
}
}

这个方法主要是:

  1. 创建一个查询,限制要同步的数据为 25 条记录。
  2. 监听指定位置上的 .ChildAdded 事件,当结果集中有新的子对象添加和即将添加时触发此事件。
  3. 从 snapshot.value 上读取 senderId 和 text。
  4. 调用 addMessage() 方法将新消息添加到数据源。
  5. 通知 JSQMessagesViewControllers(),收到一条消息。

然后,在 viewDidAppear(_:) 方法中调用 observeMessages() 方法:

override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
}

运行程序,你会看到新发送的消息会附加到已经发送的消息之后:

Firebase 教程 —— 一个实时聊天室

恭喜你!你已经有了一个实时聊天 App!现在该做一些更神奇的事了,比如说提示用户正在输入。

当用户正在输入时进行提示

这个 App 最酷的特性之一是提示“用户正在输入…”信息。会有一个小气泡弹出,告诉你用户正在键盘上敲击。这个提示非常重要,因为它让我们减少了许多诸如“还在吗?”之类的不必要的消息。

有许多方法可以检测是否正在输入,但 textViewDidChange(_:textView:) 方法是最好的方法。例如:

override func textViewDidChange(textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
print(textView.text != "")
}

要判断用户是否正在敲击键盘,只需要检查 textView.text 的值。如果这个值不为空,我们就可以认为用户正在输入。

通过 Firebase,我们可以在用户输入时向 Firebase 数据库更新状态。然后,通过从数据库检索这个状态,显示“用户正在输入”的提示。

首先在 ChatViewController 中增加几个属性:

var userIsTypingRef: Firebase! // 1
private var localTyping = false // 2
var isTyping: Bool {
get {
return localTyping
}
set {
// 3
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}

这些属性分别用于:

  1. 一个 Firebase 引用,用于存储当前用户是否正在输入。
  2. 一个私有属性,用于记录当前用户是否正在输入。
  3. 一个计算属性,通过简单地给这个属性赋值,就可以实时修改 userIsTypingRef。

在 ChatViewController 添加一个方法:

private func observeTyping() {
let typingIndicatorRef = rootRef.childByAppendingPath("typingIndicator")
userIsTypingRef = typingIndicatorRef.childByAppendingPath(senderId)
userIsTypingRef.onDisconnectRemoveValue()
}

这个方法创建了一个引用,指向 URL “/typingIndicator”,这个地址用于更新用户的输入状态。当用户退出后,我们不需要这个数据了,因此我们可以用 onDiscounnectRemoveValue() 指定,当用户离开则删除该数据。

在 viewDidAppear(_:) 方法中调用 observeTyping() 方法:

Update viewDidAppear(_:) to call observeTyping():
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
observeTyping()
}

在 ChatViewController 中添加 textViewDidChange(_:textView:) 方法,修改 isTyping 的值:

override func textViewDidChange(textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
isTyping = textView.text != ""
}

最后,在 didPressSendButton(_:withMessageText:senderId:senderDisplayName:date:) 方法最后重置输入提示:

isTyping = false

运行程序,在 Firebase App Dashboard 中观察数据。当你输入消息内容时,你会看到这个用户的 typingIndicator 会随之改变:

Firebase 教程 —— 一个实时聊天室

噢!现在你已经能够知道用户什么时候输入了!让我们来显示这个提示。

查询哪些用户正在输入

“用户正在输入”应当在用户输入的时候显示,但不应当计算本地用户。我们没有必要知道(我们已经知道)当前本地用户是否正在输入。

用一个 Firebase 查询,我们可以知道当前正在输入的所有用户。在 ChatViewController 中加入一个属性:

var usersTypingQuery: FQuery!

这个属性是一个 FQuery,跟 Firebase 引用一样,只不过它是排序的。
然后,修改 observeTyping() 为:

private func observeTyping() {
let typingIndicatorRef = rootRef.childByAppendingPath("typingIndicator")
userIsTypingRef = typingIndicatorRef.childByAppendingPath(senderId)
userIsTypingRef.onDisconnectRemoveValue()

// 1
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqualToValue(true)

// 2
usersTypingQuery.observeEventType(.Value) { (data: FDataSnapshot!) in

// 3 You're the only typing, don't show the indicator
if data.childrenCount == 1 && self.isTyping {
return
}

// 4 Are there others typing?
self.showTypingIndicator = data.childrenCount > 0
self.scrollToBottomAnimated(true)
}

}

在代码中,我们:

  1. 初始化一个查询,用于查询当前正在输入的用户。这一句相当于“喂,Firebase,去 /typingIndicator (这是一个对象,包含了若干键值对)下面看看,告诉我哪些键值对的值是 true。”
  2. 用 .Value 监听改变,一旦这些值发生任何变化,就会立即通知你。
  3. 检查结果中有多少用户正在输入。如果只有一个,则再检查这个用户是不是本地用户,如果是,不显示提示。
  4. 如果有不止一个用户,而且本地用户并没有在输入,则需要显示输入提示。最后,调用 scrollToBottomAnimated(_:animated:) 方法,确保输入提示能够被看到。

在运行程序之前,还需要一台物理设备,因为这个测试需要两台设备。用模拟器扮演一个用户,而物理设备扮演另外一个用户。

在这两台设备上(一台是模拟器,一台是物理设备)运行程序,当一个用户在输入时,你可以看到提示显示了(注意气泡中有省略号):
Firebase 教程 —— 一个实时聊天室

哇!你创建了一个伟大的、酷炫的、实时的、带用户输入提示的聊天 App。人生如此,当浮一大白!

接下来做什么

本教程代码可在此下载。

在这个教程中,你学会了如何使用 Firebase 和 JSQMessagesViewController,但仍然还有许多事情可做,比如 1 对 1 聊天,社交账号登录以及头像显示。

要进一步完善此 App,请参考 Firebase iOS 文档

如果想了解如何用社交账号登录用户,请参考Firebase 用户认证指南,其中包括了 Twitter、Google、Facebook 和 GitHub。

我希望你喜欢这篇教程,如果有任何问题,请在下面的评论中(非匿名的,而且支持头像)提出。