swiftでリアルタイムチャット

今回のチャットを作るにあたってはFirebaseという端末同志のやり取りにリアルタイム性を持たせることに特化したバックエンドサービスを使ってリアルタイムチャットを構築していきます。
Firebaseの説明、使い方はこちらの記事の「アプリケーションを登録」まで同じなのでこちら御覧ください。
http://qiita.com/ryotakodaira/items/e41c3a60348a9e1c7616

UIはJSQMessagesViewControllerというライブラリを使っていきます。
こちらのライブラリですが、素晴らしく美しいです笑

![ioschat 3.gif](https://qiita-image-store.s3.amazonaws.com/0/88163/617115f7-5e5f-7016-40ba-d76314996cb9.gif "ioschat 3.gif")


##開発環境

* xcode7.1
* swift2.1

##ライブラリをインストール

| ライブラリ	 | バージョン |
|:-----------|------------:|:------------:|
| JSQMessagesViewController	       | 7.2.0 |
| Firebase     |      2.4.3 |

FirebaseとJSQMessagesViewControllerはGithubからダウンロードして手動でインストールするか
CocoaPodsを使ってインストールすることが出来ます。

```
pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController', :tag => '7.2.0'
pod 'Firebase', '>= 2.4.3'
```


swiftファイルでインストールしたライブラリを呼び出すには以下のコードを入力します。


```
import JSQMessagesViewController
import Firebase
```

##Firebaseについて
基本的な文法を紹介していきたいと思います。
もっと詳しい文法については[Firebase公式サイト](https://www.firebase.com/docs/ios/)で公開されています。（全て英語ですが。。笑）


###Firebaseライブラリを読み込む


```
import Firebase
```

###データベースと接続する

docs-examplesを自分が作成したアプリケーション名に変更します。

```
var myRootRef = Firebase(url:"https://docs-examples.firebaseio.com/")
```

###データ保存
データベースに対して、setValue関数でデータを保存します。

```swift
//使用するDBのパスを定義
//今回の場合はhttps://docs-examples.firebaseio.com/posts　になります。
let postRef = myRootRef.childByAppendingPath("posts")

//保存したデータをDictionary型でつくる
let post1 = ["author": "gracehop", "title": "Announcing COBOL, a New Programming Language"]
let post1Ref = postRef.childByAutoId()
    
//セーブを実行する
post1Ref.setValue(post1)
```

以下の様なjsonになってDBに保存されます。

```json
{
  "posts": {
    {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    }
  }
}
```
###データの取得
データベースに何らかのデータ変更があった場合にリアルタイムにコールバック関数が実行され、変更分のデータを取得出来ます。

```swift
ref.observeEventType(.ChildAdded, withBlock: { snapshot in
    println(snapshot.value.objectForKey("author"))
    println(snapshot.value.objectForKey("title"))
})
```

##JSQMessagesViewControllerの初期設定
ViewController.swiftでJSQMessagesViewControllerを使えるようにします。

```
import JSQMessagesViewController
```

JSQMessagesViewControllerクラスを継承します。
以降の記述は全てこのクラスに書いていきます。

```
class ViewController: JSQMessagesViewController{
}
```

StoryboardからJSQMessagesViewControllerを使いたいページのCustom ClassにViewControllerを指定してください。

![スクリーンショット 2015-12-01 20.44.51.png](https://qiita-image-store.s3.amazonaws.com/0/88163/2fece4f2-01ef-6243-4d72-5637f46840fc.png "スクリーンショット 2015-12-01 20.44.51.png")

これで設定は完了です。

##ソースコード

```swift:ViewController.swift
import UIKit
import JSQMessagesViewController
import Firebase

class ViewController: JSQMessagesViewController {
    
    var ref: Firebase!
    
    var messages: [JSQMessage]?
    var incomingBubble: JSQMessagesBubbleImage!
    var outgoingBubble: JSQMessagesBubbleImage!
    var incomingAvatar: JSQMessagesAvatarImage!
    var outgoingAvatar: JSQMessagesAvatarImage!
    
    func setupFirebase() {
        
        // firebaseのセットアップ
        ref = Firebase(url: "https://docs-examples.firebaseio.com/")
        
        // 最新25件のデータをデータベースから取得する
        // 最新のデータ追加されるたびに最新データを取得する
        ref.queryLimitedToLast(25).observeEventType(FEventType.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
            print(snapshot.value!)
            let message = JSQMessage(senderId: sender, displayName: name, text: text)
            self.messages?.append(message)
            self.finishReceivingMessage()
        })
    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        inputToolbar!.contentView!.leftBarButtonItem = nil
        automaticallyScrollsToMostRecentMessage = true
        
        
        //自分のsenderId, senderDisokayNameを設定
        self.senderId = "user1"
        self.senderDisplayName = "hoge"
        
        //吹き出しの設定
        let bubbleFactory = JSQMessagesBubbleImageFactory()
        self.incomingBubble = bubbleFactory.incomingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleLightGrayColor())
        self.outgoingBubble = bubbleFactory.outgoingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleBlueColor())
        
        //アバターの設定
        self.incomingAvatar = JSQMessagesAvatarImageFactory.avatarImageWithImage(UIImage(named: "Swift-Logo")!, diameter: 64)
        self.outgoingAvatar = JSQMessagesAvatarImageFactory.avatarImageWithImage(UIImage(named: "Swift-Logo")!, diameter: 64)
        
        //メッセージデータの配列を初期化
        self.messages = []
        setupFirebase()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    //Sendボタンが押された時に呼ばれる
    override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
        
        //メッセージの送信処理を完了する(画面上にメッセージが表示される)
        self.finishReceivingMessageAnimated(true)
        
        //firebaseにデータを送信、保存する
        let post1 = ["from": senderId, "name": senderDisplayName, "text":text]
        let post1Ref = ref.childByAutoId()
        post1Ref.setValue(post1)
        
    }
    
    //アイテムごとに参照するメッセージデータを返す
    override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
        return self.messages?[indexPath.item]
    }
    
    //アイテムごとのMessageBubble(背景)を返す
    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)!
    }
    
    
}

```

##実行結果

![ioschat.gif](https://qiita-image-store.s3.amazonaws.com/0/88163/76344477-e18e-ca2f-4a76-d2422ee9983d.gif "ioschat.gif")


左側はブラウザから送信していますが、
ブラウザとiOSシュミレーターの間でリアルタイムでテキスト情報の送受信をされているのが分かるかと思います。
iOSシュミレーターの方ではSendをおした後にTextFieldがクリアされないのが課題です。。。

##さいごに
今後はサーバーサイドの言語を使ってユーザー認証を付けて、LINEやMessengerの様にユーザーを選んでそのユーザーとのチャットが出来る様なアプリの作り方を共有していきます。
この記事の内容に間違いや質問があればコメントをしていただけたらと思います！