- 原文链接 : Firebase Tutorial: Real-time Chat
- 原文作者 : David East
- 译者 : kmyhy
现在主流的 App 都开始支持聊天功能了——你的 App 是不是也该支持一下?
但是,制作一个聊天工具确实不是一件简单的任务。我们不但缺乏现成的专门针对聊天的 UIKit 组件,还需要一个服务器来负责处理用户间的消息及对话。
幸运的是,我们可以使用一个优秀的框架:Firebase。它能为我们同步实时数据而无需编写任何服务端代码,同时还提供一个 JSQMessagesViewController 用于显示消息,这个 UI 可以和本地的消息应用相媲美。
在这个 Firebase 教程中,我们会创建一个可以进行匿名聊天的 App 叫做 ChatChat,如下图所示:
最终你将学习到:
- 用 CocoaPods 安装 Firebase SDK 和 JSQMessagesViewController。
- 用 Firebase 数据库来同步实时数据。
- 让 Firebase 支持匿名登录。
- 用 JSQMessagesViewController 实现 UI。
- 当用户进行输入时,进行提示。
好了,接下来就开始吧!
开始
在开始本教程之前,请下载开始项目,目前,它只完成了一个假的登录界面。
我们可以用 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 App。就本教程而言,你需要使用实时数据库和身份认证服务。
开启匿名认证
Firebase 允许用户通过 email 地址或社交账号进行登录,但也提供匿名登录功能,后者会给每个用户分配一个唯一的 ID 但不需要用户的输入任何个人信息。
匿名认证就好比说:“我不知道你是谁,我只知道你是一个人。”。对于访问账户或者使用用户来说,这是非常方便的。这对于本教程来说非常适合,因为 ChatChat 中所有用户都是匿名的。
要开启匿名认证,你需要打开 Firebase App Dashboard,选择 Login & Auth 标签,点击 Anonymous,然后勾上 Enable Anonymous User Authentication 选项:
这样,你就开启了超级隐身模式,也就是匿名认证——很爽吧:]
登录
打开 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
}
}
上面的代码是什么意思?
- 首先,定义一个属性,存放 Firebase database 的引用。
- 然后,用你的 Firebase App URL 创建一个 Firebase 数据库连接并赋给这个属性。
如果你不知道你的 Firebase App URL 是什么,你看一下 Firebase App Dashboard :
要登录一个用户,可以在数据库引用对象上调用 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
}
}
在这个方法中,完成了如下工作:
- 调用 ref 的 authAnonyouslyWithCompletionBlock(_:) 方法,以匿名方式登录一个用户。
- 检查是否认证失败。
- 在闭包中,调用 segue 跳转到 ChatViewController。
你可能会觉得奇怪:为什么代码中会忽略了 authData 对象。在这个对象中包含了用户的唯一标识,但你可以暂时把它扔到一边。因为当用户登录成功后,我们还可以通过 Firebase 数据库引用的 authData 属性来访问 authData 对象,例如:
// 登录成功后,打印当前登录用户的信息
print(ref.authData)
创建聊天界面
JSQMessagesViewController 是一个 UICollectionViewController 的封装,为聊天进行了专门的定制。
本教程将主要介绍 5 个步骤:
- 创建消息数据
- 创建带背景色的消息气泡
- 删除头像
- 改变 UICollectionViewCell 的文字颜色
- 提示用户正在输入
几乎每个步骤都需要覆盖一些方法。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
}
在上面的代码中:
- 获取 segue 的目标 View Controller 并转换成 UINavigationController。
- 将 Navigation Controller 的第一个 View Controller 转换成 ChatViewController。
- 将本地用户的 ID 赋给 chatVc.senderId,这是 JSQMessagesViewController 用于处理消息的客户端 ID。
- 将 chatVc.senderDisplayName 设为空字符串,因为我们的聊天室是匿名登录的。
注意每个 App 会话中,我们只会有一个匿名的会话。每次重启 App 之后,你都会获得一个新的、唯一的匿名用户。如果你重启模拟器,你会看到另外一个用户 ID。
运行程序,检查你的 App 是否运行在超级隐身模式:
通过简单地继承下 JSQMessagesViewController,你就获得了一个完整的聊天 UI。太爽了!
创建数据源和委托
现在,我们有了一个聊天 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
}
}
逐行分析上面的代码:
- 根据 NSIndexPath 检索出对应的消息数据。
- 判断这条消息是否是本客户端所发出的,如果是,返回“发出消息”的 Image View。
- 如果不是,则返回“接收消息”的 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”。
运行程序,当你登入聊天室后,会看到一个空白的聊天界面:
接下来开始对话并发送几条消息!
发送消息
在 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()
}
运行程序,你会看到会话窗口中显示了几条聊天消息:
呃,接收消息中的文字也太不显眼了。最好将它设置成黑色。
消息气泡中的文字
正如你所见,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 数据库是 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()
}
这个方法负责:
- 通过 childByAutoId(),我们获得一个子对象引用,该对象有一个自动创建的唯一 key。
- 用一个字典来保存消息。一个 [String:AnyObject] 足以表示一个 JSON 对象。
- 将字典保存到新的子引用中。
- 播放一个经典的代表“消息已发送”的声音。
- 完成“发送”动作,将输入栏置空。
运行程序,打开你的 Firebase App Dashboard,点击 Data 栏。在 App 中发送一条消息,你立即会在 Dashboard 中看到这条消息显示。
成功了!你已经很“专业”地将消息保存到了 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
}
这段代码主要是:
- 通过 Firebase App URL,我们创建了一个 Firebase 数据库引用。我们指定的这个 URL 指向了我们想读取的数据。
- 用 FEventType.ChildAdded 参数调用 observeEventType(_:FEventType:) 方法。在每当位于该 URL 的对象添加了新的子对象时都会触发一次 child-added 事件。
- 闭包中会传入一个 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()
}
}
这个方法主要是:
- 创建一个查询,限制要同步的数据为 25 条记录。
- 监听指定位置上的 .ChildAdded 事件,当结果集中有新的子对象添加和即将添加时触发此事件。
- 从 snapshot.value 上读取 senderId 和 text。
- 调用 addMessage() 方法将新消息添加到数据源。
- 通知 JSQMessagesViewControllers(),收到一条消息。
然后,在 viewDidAppear(_:) 方法中调用 observeMessages() 方法:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
}
运行程序,你会看到新发送的消息会附加到已经发送的消息之后:
恭喜你!你已经有了一个实时聊天 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)
}
}
这些属性分别用于:
- 一个 Firebase 引用,用于存储当前用户是否正在输入。
- 一个私有属性,用于记录当前用户是否正在输入。
- 一个计算属性,通过简单地给这个属性赋值,就可以实时修改 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 查询,我们可以知道当前正在输入的所有用户。在 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)
}
}
在代码中,我们:
- 初始化一个查询,用于查询当前正在输入的用户。这一句相当于“喂,Firebase,去 /typingIndicator (这是一个对象,包含了若干键值对)下面看看,告诉我哪些键值对的值是 true。”
- 用 .Value 监听改变,一旦这些值发生任何变化,就会立即通知你。
- 检查结果中有多少用户正在输入。如果只有一个,则再检查这个用户是不是本地用户,如果是,不显示提示。
- 如果有不止一个用户,而且本地用户并没有在输入,则需要显示输入提示。最后,调用 scrollToBottomAnimated(_:animated:) 方法,确保输入提示能够被看到。
在运行程序之前,还需要一台物理设备,因为这个测试需要两台设备。用模拟器扮演一个用户,而物理设备扮演另外一个用户。
在这两台设备上(一台是模拟器,一台是物理设备)运行程序,当一个用户在输入时,你可以看到提示显示了(注意气泡中有省略号):
哇!你创建了一个伟大的、酷炫的、实时的、带用户输入提示的聊天 App。人生如此,当浮一大白!
接下来做什么
本教程代码可在此下载。
在这个教程中,你学会了如何使用 Firebase 和 JSQMessagesViewController,但仍然还有许多事情可做,比如 1 对 1 聊天,社交账号登录以及头像显示。
要进一步完善此 App,请参考 Firebase iOS 文档。
如果想了解如何用社交账号登录用户,请参考Firebase 用户认证指南,其中包括了 Twitter、Google、Facebook 和 GitHub。
我希望你喜欢这篇教程,如果有任何问题,请在下面的评论中(非匿名的,而且支持头像)提出。