Firebase 教程 —— 一個實時聊天室

TamelaBunde 8年前發布 | 28K 次閱讀 Firebase iOS開發 移動開發

原文是基于Firebase 2.X構筑的匿名聊天室Demo,我在根據原文構筑時FireBase已經更新到3.X,谷歌對Firebase SDK做了不小的改動,所有的功能都由不同的類來操作,不再由Firebase類統一調度,這些改動致使原文某些地方變得不合時宜。

因此,我將自己根據原文構筑的基于3.X Firebase的流程及所遇到的坑穿插在原文中并且重新構筑了原文的部分代碼。(有沒有覺得構筑這兩個字很有逼格?!)

現在主流的 App 都開始支持聊天功能了——你的 App 是不是也該支持一下?

但是,制作一個聊天工具確實不是一件簡單的任務。我們不但缺乏現成的專門針對聊天的 UIKit 組件,還需要一個服務器來負責處理用戶間的消息及對話。

幸運的是,我們可以使用一個優秀的框架:Firebase。它能為我們同步實時數據而無需編寫任何服務端代碼,同時還提供一個 JSQMessagesViewController 用于顯示消息,這個 UI 可以和本地的消息應用相媲美。

在這個 Firebase 教程中,我們會創建一個可以進行匿名聊天的 App 叫做 ChatChat,如下圖所示:

最終你將學習到:

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"

茄子注:個人使用CocoaPod安裝FireBase時慘遭谷歌翻臉,然后在手動集成時又被官方文檔坑了,在此將手動集成時的一些注意點根據自己手動集成的流程說明。

首先,你可以在 這里 下載Firebase SDK

選擇其中一些組件或者像我一樣直接把整個包丟進項目中。

接著第三步,將ObjC鏈接器標志添加到Other Linker Settings中。

這一步需要注意的是,你需要在Project中及使用到Firebase的Targets中都將ObjC鏈接器標志添加到Other Linker Settings中。

接著,Run一下,是否Crash及控制臺報告如下:

Configuring the default app.
<FIRAnalytics/DEBUG> Debug mode is on
<FIRAnalytics/INFO> Firebase Analytics v.3301000 started
<FIRAnalytics/INFO> To enable debug logging set the following application argument: -FIRAnalyticsDebugEnabled (see http://goo.gl/Y0Yjwu)
<FIRAnalytics/DEBUG> Debug logging enabled
<FIRAnalytics/DEBUG> Monitoring the network status
Firebase Crash Reporting: Successfully enabled
//導致Crash的元兇
 A reversed client ID should be added as a URL scheme to enable Google sign-in.

我們需要將注冊FireBase項目時獲得的GoogleService-Info.plist中間中的REVERSED_CLIENT_ID添加到Info -> URL Types -> URL Schemes中,如下圖:

再次運行你就能看到正常的運行畫面:

注意:在接下來的教程中,你每次編譯和運行都會看到這個界面。點擊“匿名登錄”又會切換到另一個界面。目前點擊按鈕沒有什么用處,但隨后我們就會實現它。

如果你第一次接觸 Firebase,你需要創建一個賬號。不用擔心—— 它非常簡單,而且完全是免費的,不需要信用卡。

注意:關于如何注冊 Firebase 的完整步驟,請看我們的 Firebase 入門教程

注冊 Firebase 賬號

進入 Firebase 注冊頁面 ,創建一個賬號,然后創建一個 Firebase App。就本教程而言,你需要使用實時數據庫和身份認證服務。

開啟匿名認證

Firebase 允許用戶通過 email 地址或社交賬號進行登錄,但也提供匿名登錄功能,后者會給每個用戶分配一個唯一的 ID 但不需要用戶的輸入任何個人信息。

匿名認證就好比說:“我不知道你是誰,我只知道你是一個人。”。對于訪問賬戶或者使用用戶來說,這是非常方便的。這對于本教程來說非常適合,因為 ChatChat 中所有用戶都是匿名的。

要開啟匿名認證,你需要進入你的 Firebase 項目 ,選擇 Auth 標簽,若是初次創建則會指引你選擇登陸方法,啟用匿名登陸即可。

這樣,你就開啟了超級隱身模式,也就是匿名認證——很爽吧:]

登陸

打開 LoginViewController.Swift,加入:

import Firebase

要登入聊天室,需要連接到 Firebase 數據庫。在 LoginViewController.swift 中加入:

class LoginViewController: UIViewController {

    var ref: FIRDatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
        ref = FIRDatabase.database().referenceFromURL("https://fir-demo-43879.firebaseio.com/")    
    }

上面的代碼是什么意思?

首先,定義一個屬性,存放 Firebase database 的引用。

然后,用你的 Firebase App URL 創建一個 Firebase 數據庫連接并賦給這個屬性。

如果你不知道你的 Firebase App URL 是什么,就像上一步一樣點擊Database即可 :

茄子注,雖然Firebase沒有向我們提供Database的源代碼,但從其API上大致可以猜測一二,使用URL進行首次連接,接著在注銷之前便會無限制地互發數據,這點上與WebSocket的機制非常相似,我們有理由猜測FireDatabase的底層是利用WebSocket的概念實現的。

要登錄一個用戶,可以在數據庫引用對象上調用 authAnonymouslyWithCompletionBlock(_:)。

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

@IBAction func loginDidTouch(sender: AnyObject) {
    FIRAuth.auth()?.signInAnonymouslyWithCompletion({ (user, error) in
        if error != nil { print(error?.description); return }
        print(user?.uid)
        self.performSegueWithIdentifier("LoginToChat", sender: nil) // 3
    })
  }

在這個方法中,完成了如下工作:

1、調用 FiRAuth的單例 的 authAnonyouslyWithCompletionBlock(_:) 方法以匿名方式登錄一個用戶。

2、檢查是否認證失敗。

3、在閉包中,調用 segue 跳轉到 ChatViewController。

閉包回調中的user包含了用戶的唯一標示符及是否匿名登陸等重要信息,當我們在使用到時可以通過:

FIRAuth.auth()?.currentUser

來獲得相應信息。

茄子注:匿名登陸并不會隨時改變,就像一般的賬戶一樣通過一個refreshToken來實現長期登陸,這個屬性被加密成無規則字符串。

創建聊天界面

JSQMessagesViewController 是一個 UICollectionViewController 的封裝,為聊天進行了專門的定制。

本教程將主要介紹 5 個步驟:

1、創建消息數據

2、創建帶背景色的消息氣泡

3、刪除頭像

4、改變 UICollectionViewCell 的文字顏色

5、提示用戶正在輸入

幾乎每個步驟都需要覆蓋一些方法。JSQMessagesViewController 使用了JSQMessagesCollectionViewDataSource 協議,因此我們需要覆蓋協議的默認實現就可以了。

注意:關于 JSQMessagesCollectionViewDataSource 的更多內容,請參考 這里

打開 ChatViewController.swift 導入 Firebase 和JSQMVC :

import Firebase
import JSQMessagesViewController

將父類從 UIViewController 類修改 JSQMessagesViewController 類:

class ChatViewController: JSQMessagesViewController {

現在 ChatViewController 繼承了 JSQMessagesViewController,我們可以設置 senderId 和 senderDisplayName 的初始值了,這樣 App 才能唯一識別消息的發送者——否則它無從知道發送者是誰。

在 LoginViewController 中,我們用 user 將用戶信息傳遞給 ChatViewController(通過 prepareForSegue 方法)。

在 LoginViewController 中添加方法:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        super.prepareForSegue(segue, sender: sender)
        guard let naV = segue.destinationViewController as? UINavigationController else { return }
        guard let chatVC = naV.viewControllers.first as? ChatViewController else { return }
        chatVC.senderId = FIRAuth.auth()?.currentUser?.uid
        chatVC.senderDisplayName = ""
    }

在上面的代碼中:

1、獲取 segue 的目標 View Controller 并轉換成 UINavigationController。

2、將 Navigation Controller 的第一個 View Controller 轉換成ChatVC。

3、將本地用戶的 ID 賦給 chatVc.senderId,這是 JSQMVC 用于處理消息的客戶端 ID。

4、將 chatVc.senderDisplayName 設為空字符串,因為我們的聊天室是匿名登錄的。

注意每個 App 會話中,我們只會有一個匿名的會話。每次重啟 App 之后,你都會獲得一個新的、唯一的匿名用戶。如果你重啟模擬器,你會看到另外一個用戶 ID。

茄子注:上面那段話存在于原譯文之中,我沒有與英文原文核實是否英文原文存在。有可能是Firebase的機制變化了,我們可以從不斷重啟模擬器、打印user.uid及通過Firebase項目的Auth頁面查看驗證得到user.uid沒有發生變化,因此每次重啟App之后都會得到一個新的匿名用戶是不正確的。

運行程序,檢查你的 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 = FIRDatabase.database().referenceFromURL("https://<my-firebase-app>.firebaseio.com/messages/")
    var messageRef:FIRDatabaseReference!

譯者注:將 \ 替換成你自己的 Firebase App ID。

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

override func viewDidLoad() {
        super.viewDidLoad()
        title = "ChatChat"
        setupBubbles()
        collectionView.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
        collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
        messageRef = rootRef.child("messages")
    }

我們創建了一個 rootRef 對象用于連接 Firebase 數據庫。然后用 child() 方法創建了一個 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()
        let messageItem = [
        "text":text,
        "senderId":senderId
        ]
        itemRef.setValue(messageItem)
        JSQSystemSoundPlayer.jsq_playMessageSentSound()
        finishSendingMessage()
    }

這個方法負責:

1、通過 childByAutoId(),我們獲得一個子對象引用,該對象有一個自動創建的唯一 key。

2、用一個字典來保存消息。一個 [String:AnyObject] 足以表示一個 JSON 對象。

3、將字典保存到新的子引用中。

4、播放一個經典的代表“消息已發送”的聲音。

5、完成“發送”動作,將輸入欄置空。

運行程序,打開你的 Firebase App Dashboard,點擊 Data 欄。在 App 中發送一條消息,你立即會在 Dashboard 中看到這條消息顯示。

茄子注:抱歉,實在不想重制gif,麻煩得要死ORZ。

成功了!你已經很“專業”地將消息保存到了 Firebase 數據庫。這個消息還沒有顯示到 iPhone 上,但我們接下來就會這樣做。

與 Firebase 保持實時同步

每當我們改變 Firebase 數據庫中的數據,數據庫就會將修改 push 給每個已經連接的 App 上。Firebase 的數據同步機制分為三個部分:URL、事件和快照。

例如,你可以用這種方式來監聽新的消息:

let rootRef = FIRDatabase.database().referenceFromURL("https://fir-demo-43879.firebaseio.com/")
        rootRef.observeEventType(.ChildChanged) { (snapshot:FIRDataSnapshot) in
            print(snapshot.value)
        }

這段代碼主要是:

通過 Firebase App URL,我們創建了一個 Firebase 數據庫引用。我們指定的這個 URL 指向了我們想讀取的數據。

用 FEventType.ChildAdded 參數調用 observeEventType(_:FEventType:) 方法。在每當位于該 URL 的對象添加了新的子對象時都會觸發一次 child-added 事件。

閉包中會傳入一個 FDataSnapshot 對象,這個對象中會包含有相應的數據以及一些有用的方法。

同步數據源

看到了吧,和 Firebase 保持數據同步是非常簡單的,接下來是和數據源進行對接。

在 ChatViewController 中增加一個方法:

//監聽消息
    func observeMessages() {
        //1
        let messagesQuery = messageRef.queryLimitedToLast(25)
        //2
        messagesQuery.observeEventType(.ChildAdded) { [weak self] (snpaShot:FIRDataSnapshot) in
            //3
            guard let dict = snpaShot.value as? [String:AnyObject] else { return }
            guard let id = dict["senderId"] as? String else { return }
            guard let text = dict["text"] as? String else { return }
            //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()
}

運行程序,你會看到新發送的消息會附加到已經發送的消息之后:

恭喜你!你已經有了一個實時聊天 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:FIRDatabaseReference! //1
    private var localTyping = false //2
    var isTyping: Bool {
        set{
            //3
            localTyping = newValue
            userIsTypingRef.setValue(newValue)
        }
        get{
            return localTyping
        }
    }

這些屬性分別用于:

1、一個 FIRDatabaseReference 引用,用于存儲當前用戶是否正在輸入。

2、一個私有屬性,用于記錄當前用戶是否正在輸入。

3、一個計算屬性,通過簡單地給這個屬性賦值,就可以實時修改 userIsTypingRef。

在 ChatViewController 添加一個方法:

//監聽輸入
    func observeIsTyping() {
        let typingIndicatorRef = rootRef.child("typingIndicator")
        userIsTypingRef = typingIndicatorRef.child(senderId)
        userIsTypingRef.onDisconnectRemoveValue()
    }

這個方法創建了一個引用,指向 URL “/typingIndicator”,這個地址用于更新用戶的輸入狀態。當用戶退出后,我們不需要這個數據了,因此我們可以用 onDiscounnectRemoveValue() 指定,當用戶離開則刪除該數據。

在 viewDidAppear(_:) 方法中調用 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 userTypingQuery:FIRDatabaseQuery!

然后,修改 observeTyping() 為:

//監聽輸入
    func observeIsTyping() {
        let typingIndicatorRef = rootRef.child("typingIndicator")
        userIsTypingRef = typingIndicatorRef.child(senderId)
        userIsTypingRef.onDisconnectRemoveValue()

        //1
        userTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqualToValue(true)
        //2
        userTypingQuery.observeEventType(.Value) { [weak self] (snapShot:FIRDataSnapshot) in
            if let weakself = self {

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

                // 4 Are there others typing?
                weakself.showTypingIndicator = snapShot.childrenCount > 1
                weakself.scrollToBottomAnimated(true)
            }
        }
    }

在代碼中,我們:

1、初始化一個查詢,用于查詢當前正在輸入的用戶。這一句相當于“喂,Firebase,去 /typingIndicator (這是一個對象,包含了若干鍵值對)下面看看,告訴我哪些鍵值對的值是 true。”

2、用 .Value 監聽改變,一旦這些值發生任何變化,就會立即通知你。

3、檢查結果中有多少用戶正在輸入。如果只有一個,則再檢查這個用戶是不是本地用戶,如果是,不顯示提示。

4、如果有不止一個用戶,而且本地用戶并沒有在輸入,則需要顯示輸入提示。最后,調用 scrollToBottomAnimated(_:animated:) 方法,確保輸入提示能夠被看到。

在運行程序之前,還需要一臺物理設備,因為這個測試需要兩臺設備。用模擬器扮演一個用戶,而物理設備扮演另外一個用戶。

在這兩臺設備上(一臺是模擬器,一臺是物理設備)運行程序,當一個用戶在輸入時,你可以看到提示顯示了(注意氣泡中有省略號):

哇!你創建了一個偉大的、酷炫的、實時的、帶用戶輸入提示的聊天 App。人生如此,當浮一大白!

在這個教程中,你學會了如何使用 Firebase 和 JSQMessagesViewController,但仍然還有許多事情可做,比如 1 對 1 聊天,社交賬號登錄以及頭像顯示。

 

 

來自:http://www.jianshu.com/p/98eb3356593b

 

 本文由用戶 TamelaBunde 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!