Goalist Developers Blog

寿司とチャットするアプリを作ってみた

こんちは。渡部です。
エンジニアの勉強会に行くとよく寿司好きの人に出会います。

f:id:watabe1028:20170531150310p:plain

なので?今回は寿司とチャットするアプリをささっと作ってみます。
ただしローカルでのみ動くものなので固定文言でやります。

チャット画面を1から作るのは大変なので
JSQMessagesViewControllerというライブラリを使って作ります。

こんな感じでやっていきます
・JSQMessagesViewControllerってなに?うまいの?
・どうやって使うのよ?
・JSQMessagesViewControllerをプロジェクトに追加する
・適当にチャット文言を用意する
・ガリガリ書く寿司だけに

れっつらゴー!

JSQMessagesViewControllerってなに?うまいの?

JSQMessagesViewController

github.com

簡単にいうとチャット画面を提供してくれるライブラリです。
なので実装は文言やアイコンなどの設定をすれば
最低限の機能が簡単に使えます。

どうやって使うのよ?

GitHubで落としてゴー!

JSQMessagesViewControllerをプロジェクトに追加する

CocoaPodsで落とします。
使い方がわからない人はこちら

プロジェクトを作ったらPodsでJSQMessagesViewControllerをinstallします。
サンプルとして「ChatTestApp」を作ります。

作ったらターミナルでアプリ直下まで行き、Podsを作成します。

pod init

作成したらJSQMessagesViewControllerを落とすため編集モードで追記し、installします。
編集モードにして、

vi Podfile

これを追記する。

  # Pods for TestApp
  pod ‘JSQMessagesViewController’
end

「:wq」で保存、終了。

インストール!

pod install

これで下準備おけ!

適当にチャット文言を用意する

まんまです。
文言をこんな感じに用意します。

var messages: [JSQMessage] = [
        JSQMessage(senderId: "sushi", displayName: "B", text: "らっしゃい♪"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "大将!つぶ貝!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ないよ!"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "じゃサーモン!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ないよ♪"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "いか!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ないよ♪"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "じゃ帰る!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ちょwww待てってwww\nマグロあるからwww")
    ]

senderIdの"sushi"が寿司アイコン(相手)、"Dummy"が自分です。
displayNameは表示名ですが、シンプルな方が好きなので
表示させないようにあとで設定します。私情です。
textはチャットで表示される文言です。

ガリガリ書く寿司だけに

コピペでできるように最低限のコードのみ記載します。
説明はコメントを見てもらえばわかるかと。

import JSQMessagesViewController // インポートする

class ViewController: JSQMessagesViewController { // ViewControllerからJSQMessagesViewControllerに変更する
    
    var messages: [JSQMessage] = [
        JSQMessage(senderId: "sushi", displayName: "B", text: "らっしゃい♪"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "大将!つぶ貝!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ないよ!"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "じゃサーモン!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ないよ♪"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "いか!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ないよ♪"),
        JSQMessage(senderId: "Dummy",  displayName: "A", text: "じゃ帰る!"),
        JSQMessage(senderId: "sushi", displayName: "B", text: "ちょwww待てってwww\nマグロあるからwww")
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 適当につける
        senderDisplayName = "A"
        senderId = "Dummy"
    }

     //アイテムごとに参照するメッセージデータを返す
    override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
        return messages[indexPath.row]
    }


     //アイテムごとのMessageBubble(背景)を返す
    override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
        if messages[indexPath.row].senderId == senderId {
            return JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(
                with: UIColor(red: 112/255, green: 192/255, blue:  75/255, alpha: 1))
        } else {
            return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(
                with: UIColor(red: 229/255, green: 229/255, blue: 229/255, alpha: 1))
        }
    }


     // cell for item
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
        
        if messages[indexPath.row].senderId == senderId {
            cell.textView?.textColor = UIColor.white
        } else {
            cell.textView?.textColor = UIColor.darkGray
        }
        return cell
    }
    
    
    // section
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return messages.count
    }


     // image data for item
    override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
        
        //senderId == 自分 だった場合表示しない
        let senderId = messages[indexPath.row].senderId
        
        if senderId == "Dummy" {
            return nil
        }
        return JSQMessagesAvatarImage.avatar(with: UIImage(named: "sushi"))
    }


     //時刻表示のための高さ調整
    override func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForCellTopLabelAt indexPath: IndexPath!) -> NSAttributedString! {
        
        let message = messages[indexPath.item]
        if indexPath.item == 0 {
            return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: message.date)
        }
        if indexPath.item - 1 > 0 {
            let previousMessage = messages[indexPath.item - 1]
            if message.date.timeIntervalSince(previousMessage.date) / 60 > 1 {
                return JSQMessagesTimestampFormatter.shared().attributedTimestamp(for: message.date)
            }
        }
        return nil
    }


     // 送信時刻を出すために高さを調整する
    override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellTopLabelAt indexPath: IndexPath!) -> CGFloat {
        
        if indexPath.item == 0 {
            return kJSQMessagesCollectionViewCellLabelHeightDefault
        }
        if indexPath.item - 1 > 0 {
            let previousMessage = messages[indexPath.item - 1]
            let message = messages[indexPath.item]
            if message.date .timeIntervalSince(previousMessage.date) / 60 > 1 {
                return kJSQMessagesCollectionViewCellLabelHeightDefault
            }
        }
        return 0.0
    }

    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

画面読み込みですぐにチャットが表示されているはずです。

次にチャットを送受信させて見ます。

     //Sendボタンが押された時に呼ばれる
    override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
        
        //キーボードを閉じる
        self.view.endEditing(true)
        
        //メッセージを追加
        let message = JSQMessage(senderId: senderId, displayName: senderDisplayName, text: text)
        self.messages.append(message!)
        
        //送信を反映
        self.finishReceivingMessage(animated: true)
        
        //textFieldをクリアする
        self.inputToolbar.contentView.textView.text = ""
        
        //テスト返信を呼ぶ
        testRecvMessage()
    }

     //テスト用「マグロならあるよ!」を返す
    func testRecvMessage() {
        
        let message = JSQMessage(senderId: "sushi", displayName: "B", text: "マグロならあるよ!")
        self.messages.append(message!)
        self.finishReceivingMessage(animated: true)
    }

こんな感じになります。
「うに!」と入力し、Sendボタンを押下で、、、

f:id:watabe1028:20170531175132p:plain

こうなる!

f:id:watabe1028:20170531175207p:plain

f:id:watabe1028:20170531175525p:plain

まとめ

世に出てるチャットアプリの大半はJSQMessagesViewControllerを使っているんではないかなーと予想しています。
これからも楽できる便利なライブラリの紹介ができたらなと思います。