Firebaseとは

Firebaseは2014年にGoogleに買収されたBaas(Backend as a service)です。 このFirebaseには、様々な機能が実装されております。

今回は、その中の1つであるリアルタイムデータベースを用いてチャットを実装します。

今回のゴール

Swiftで異なるiOSデバイス間のリアルタイムチャットを実現する

Firebaseの導入

Firebase(https://firebase.google.com/)へアクセスして、アカウントを作成します。 新規プロジェクト作成を選び、プロジェクト名と国を選択します。

プロジェクト作成

するとこんな感じで作成されます。

ここで、あらかじめXcodeで新しいプロジェクトを作成しておきましょう。 作成が済んだら、Firebaseに戻って、先ほどのプロジェクトを選択します。「AndroidアプリにFirebaseを追加」「iOSアプリにFirebaseを追加」「ウェブアプリにFirebaseを追加」と選択肢が出てくるので、今回は「iOSアプリにFirebaseを追加」を選択します。

iOSのバンドルIDを入力し、AppStoreIDは未入力にしておきます。決定ボタンを押すと設定ファイルがDLされます。

初期設定

先ほどダウンロードした設定ファイルを、Xcodeのプロジェクトのルートにコピーします。 コピーが終わったら、podでライブラリを導入していきます。

Podfile

# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

target 'chat-firebase-ios' do
  use_frameworks!

  # Firebase
  pod 'Firebase'
  pod 'Firebase/Database'

  target 'chat-firebase-iosTests' do
    inherit! :search_paths
  end

  target 'chat-firebase-iosUITests' do
    inherit! :search_paths
  end

end

Pod installでライブラリを追加します。 完了したら、初期化コードを追加しましょう。

AppDelegate.swift

import UIKit
import Firebase //追加する

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

	var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        FIRApp.configure() //追加する
        return true
	}
}

これで設定は完了です。 ほとんどガイドに従っておけばいいので、簡単ですね!

チャットのUI実装

続いては、チャット画面を実装します。 チャットのUIについて、今回はJSQMessagesViewControllerを利用します。

Podfileに以下を追記し

pod 'JSQMessagesViewController'

pod installします。

ViewController.swiftにUI部分を実装していきます。

import UIKit
import Firebase
import JSQMessagesViewController

class ViewController: JSQMessagesViewController {
    
    var messages: [JSQMessage]?

    var incomingBubble: JSQMessagesBubbleImage!
    var outgoingBubble: JSQMessagesBubbleImage!
    var incomingAvatar: JSQMessagesAvatarImage!
    var outgoingAvatar: JSQMessagesAvatarImage!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupFirebase()
        setupChatUi()

        self.messages = []
    }
    
    func setupFirebase() {
        //Firebaseの設定を記述(後述)
    }
    
    func setupChatUi() {
        inputToolbar!.contentView!.leftBarButtonItem = nil
        automaticallyScrollsToMostRecentMessage = true
        
        self.senderId = "user1"
        self.incomingAvatar = JSQMessagesAvatarImageFactory.
        avatarImageWithImage(UIImage(named: "icon_default")!, diameter: 64)
        self.outgoingAvatar = JSQMessagesAvatarImageFactory.
        avatarImageWithImage(UIImage(named: "icon_default")!, diameter: 64)
        
        let bubbleFactory = JSQMessagesBubbleImageFactory()
        self.incomingBubble = bubbleFactory.incomingMessagesBubbleImageWithColor(
        	UIColor.jsq_messageBubbleLightGrayColor())
        self.outgoingBubble = bubbleFactory.outgoingMessagesBubbleImageWithColor(
        	UIColor.jsq_messageBubbleGreenColor())
    }

    //メッセージの送信
    override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
        
        self.finishSendingMessageAnimated(true)
        sendTextToDb(text)
    }

    func sendTextToDb(text: String) {
    	//データベースへの送信(後述)
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
        return self.messages?[indexPath.item]
    }
    
    override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
        let message = self.messages?[indexPath.item]
        if message?.senderId == self.senderId {
            return self.outgoingBubble
        }
        return self.incomingBubble
    }
    
    override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
        let message = self.messages?[indexPath.item]
        if message?.senderId == self.senderId {
            return self.outgoingAvatar
        }
        return self.incomingAvatar
    }
    
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return (self.messages?.count)!
    }
}
    

JSQMessagesViewControllerの詳しい使い方については今回割愛します。

チャット機能の実装

機能を実装する前に、Firebaseを少しいじります。

Firebaseで先ほど作成した、プロジェクトの画面のメニューからDatabeseを選択します。 するとデータ・ルールのタブが出現するので、ルールを選択します。 そして、ルール内部を以下のように書き換えます。

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

ルール書き換え

警告が出ますが、今回は最低限、チャットが出来るようになることがゴールなので、一旦無視します。

さて、先ほどのsetupFirebase()およびsendTextToDb(text: String)にそれぞれ機能を実装していきます。

func setupFirebase() {
    let rootRef = FIRDatabase.database().reference()
    rootRef.queryLimitedToLast(100).observeEventType(FIRDataEventType.ChildAdded, withBlock: { (snapshot) in
    	let text = snapshot.value!["text"] as! String
    	let sender = snapshot.value!["from"] as! String
    	let name = snapshot.value!["name"] as! String
    	let message = JSQMessage(senderId: sender, displayName: name, text: text)
		self.messages?.append(message)
		self.finishReceivingMessage()
    })
}

投稿データの変更に関する通知を受け取ったタイミングで、最新の投稿をUIに反映させます。

func sendTextToDb(text: String) {
	let rootRef = FIRDatabase.database().reference()
	let post = ["from": senderId,
            	"name": senderDisplayName,
            	"text": text]
	let postRef = rootRef.childByAutoId()
	postRef.setValue(post)

投稿を送信したタイミングで、データベースに投稿を書き込みます。

たったこれだけです!

早速、手動でself.senderId(送信者ID)だけ変更してビルドした2台のiPhoneで動作確認をしてみます。

動いた

できました!

まとめ

  • Firebaseにはリアルタイムデータベースという機能がある。

  • Firebaseを利用すると、手軽にチャットの実装ができる。