茶漬けの技術メモ

Golang, Rubyで趣味開発します。テックニュース書いたり。ガジェット触ったり。

【Swift】Firebaseを使ってチャットアプリを作る。4 〜メッセージを順番通りに表示する〜

この記事は、以下の記事の続きとなっております。

【Swift】Firebaseを使ってチャットアプリを作る。1 〜画面を作る〜 - 茶漬けの技術メモ

【Swift】Firebaseを使ってチャットアプリを作る。2 〜Firebase をアプリに追加する〜 - 茶漬けの技術メモ

【Swift】Firebaseを使ってチャットアプリを作る。3 〜メッセージを送受信できるようにする〜 - 茶漬けの技術メモ


前回までで、メッセージを送受信できるようになりました。
しかし、メッセージの順番がなぜかめちゃくちゃになっていました。
今回は、そこの修正をしていきます!!

辞書のmap は順番が保証されない

前回 Firebase から取得したデータを取り出すときに以下のコードを書きましたが。
調べたところ、どうやらここが問題のようです。

    self.messages = posts.values.map { dic in
        let senderId = dic["senderId"] ?? ""
        let text = dic["text"] ?? ""
        let displayName = dic["displayName"] ?? ""
        return JSQMessage(senderId: senderId,  displayName: displayName, text: text)
    }


辞書型でmap を使った場合、実行される処理は順番が保証されていないようです。
本当かどうかplayground で調べてみます。
f:id:biwako_no_otyazuke:20161130122422p:plain
確かに順番がめちゃくちゃになっていますね。

ちなみに、配列では順番が保証されています。
f:id:biwako_no_otyazuke:20161130122537p:plain

配列を利用するとメッセージを順番に表示することができそうです。

Firebase にタイムスタンプを送る

順番通りにソートするために、タイムスタンプを送信します。
「Send」ボタンを押した時の処理を以下に変更。
参考

override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
    inputToolbar.contentView.textView.text = ""
    let ref = FIRDatabase.database().reference()
    ref.child("messages").childByAutoId().setValue(["senderId": senderId, "text": text, "displayName": senderDisplayName, "date": [".sv": "timestamp"]])
}


タイムスタンプを使ってメッセージデータを順番に表示

ViewController を以下に変更します。

    var messages = [JSQMessage]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        senderDisplayName = "tsuru"
        senderId = "Tsuru"
        let ref = FIRDatabase.database().reference()
        ref.observe(.value, with: { snapshot in
            guard let dic = snapshot.value as? Dictionary<String, AnyObject> else {
                return
            }
            guard let posts = dic["messages"] as? Dictionary<String, Dictionary<String, AnyObject>> else {
                return
            }
            // keyとdateが入ったタプルを作る
            var keyValueArray: [(String, Int)] = []
            for (key, value) in posts {
                keyValueArray.append((key: key, date: value["date"] as! Int))
            }
            keyValueArray.sort{$0.1 < $1.1}             // タプルの中のdate でソートしてタプルの順番を揃える(配列で) これでkeyが順番通りになる
            // messagesを再構成
            var preMessages = [JSQMessage]()
            for sortedTuple in keyValueArray {
                for (key, value) in posts {
                    if key == sortedTuple.0 {           // 揃えた順番通りにメッセージを作成
                        let senderId = value["senderId"] as! String!
                        let text = value["text"] as! String!
                        let displayName = value["displayName"] as! String!
                        preMessages.append(JSQMessage(senderId: senderId, displayName: displayName, text: text))
                    }
                }
            }
            self.messages = preMessages
            
            self.collectionView.reloadData()
        })
    }


各データのアドレスとタイムスタンプを持ったタプルを作成し、タイムスタンプを使ってアドレスをソートしていきます。

これで順番通りにメッセージが表示されるようになりました。

f:id:biwako_no_otyazuke:20161130123857p:plain


めでたしめでたし。。。。

なのですが、

いやいやいや、そもそもposts.valuesは配列を返すだろ! と思った方がいるのではないでしょうか?
実は、posts.valuesが返すのは、配列ではなく、、、 f:id:biwako_no_otyazuke:20161130124222p:plain

LazyMapCollection というものなのです。が、
そこについてはまた別記事で書いていこうと思いますー!

チャットアプリについてはここまでです! ありがとうございましたー!!