[iOS] JSQMessagesViewController でチャットアプリを実装する

iOS

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

チャットアプリを実装!

今回は、チャット機能の UI を簡単に実装できる JSQMessagesViewController をご紹介したいと思います。

JSQMessagesViewController-Demo

基本的な使いかたは単純明快一目瞭然!さらに、カスタマイズもかなり考慮されているので手順さえ把握してしまえば自由にカスタマイズできます。

ということで、今回は一番簡単な使いかたをまとめてみました。サンプルコードは以下に公開しましたので、併せてご参照いただけるとより理解できると思います。

インストール方法

インストールは毎度おなじみ CocoaPods で簡単に。

pod 'JSQMessagesViewController'

pod install でインストール完了です。

まずは使ってみる

それでは早速使ってみましょう。まずはヘッダーから実装しましょう。ViewController は JSQMessagesViewController を継承します。このクラスは JSQMessagesCollectionViewDataSourceJSQMessagesCollectionViewDelegateFlowLayoutUITextViewDelegate の3つのプロトコルを実装した UIViewController です。UITableViewController ではない点に留意しておきましょう。

#import <JSQMessagesViewController/JSQMessages.h>

@interface ViewController : JSQMessagesViewController

@end

次に実装ファイルです。少し長いですが、分かりやすくなるよう出来る限り短いコードで書きました。コメントに番号を振っているので、その順番に解説していきます。

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) NSMutableArray *messages;
@property (strong, nonatomic) JSQMessagesBubbleImage *incomingBubble;
@property (strong, nonatomic) JSQMessagesBubbleImage *outgoingBubble;
@property (strong, nonatomic) JSQMessagesAvatarImage *incomingAvatar;
@property (strong, nonatomic) JSQMessagesAvatarImage *outgoingAvatar;

@end

@implementation ViewController

#pragma mark - UIViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // ① 自分の senderId, senderDisplayName を設定
    self.senderId = @"user1";
    self.senderDisplayName = @"classmethod";
    // ② MessageBubble (背景の吹き出し) を設定
    JSQMessagesBubbleImageFactory *bubbleFactory = [JSQMessagesBubbleImageFactory new];
    self.incomingBubble = [bubbleFactory  incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]];
    self.outgoingBubble = [bubbleFactory  outgoingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleGreenColor]];
    // ③ アバター画像を設定
    self.incomingAvatar = [JSQMessagesAvatarImageFactory avatarImageWithImage:[UIImage imageNamed:@"User2"] diameter:64];
    self.outgoingAvatar = [JSQMessagesAvatarImageFactory avatarImageWithImage:[UIImage imageNamed:@"User1"] diameter:64];
    // ④ メッセージデータの配列を初期化
    self.messages = [NSMutableArray array];
}

#pragma mark - JSQMessagesViewController

// ⑤ Sendボタンが押下されたときに呼ばれる
- (void)didPressSendButton:(UIButton *)button
           withMessageText:(NSString *)text
                  senderId:(NSString *)senderId
         senderDisplayName:(NSString *)senderDisplayName
                      date:(NSDate *)date
{
    // 効果音を再生する
    [JSQSystemSoundPlayer jsq_playMessageSentSound];
    // 新しいメッセージデータを追加する
    JSQMessage *message = [JSQMessage messageWithSenderId:senderId
                                              displayName:senderDisplayName
                                                     text:text];
    [self.messages addObject:message];
    // メッセージの送信処理を完了する (画面上にメッセージが表示される)
    [self finishSendingMessageAnimated:YES];
    // 擬似的に自動でメッセージを受信
    [self receiveAutoMessage];
}

#pragma mark - JSQMessagesCollectionViewDataSource

// ④ アイテムごとに参照するメッセージデータを返す
- (id<JSQMessageData>)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return [self.messages objectAtIndex:indexPath.item];
}

// ② アイテムごとの MessageBubble (背景) を返す
- (id<JSQMessageBubbleImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
    JSQMessage *message = [self.messages objectAtIndex:indexPath.item];
    if ([message.senderId isEqualToString:self.senderId]) {
        return self.outgoingBubble;
    }
    return self.incomingBubble;
}

// ③ アイテムごとのアバター画像を返す
- (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
    JSQMessage *message = [self.messages objectAtIndex:indexPath.item];
    if ([message.senderId isEqualToString:self.senderId]) {
        return self.outgoingAvatar;
    }
    return self.incomingAvatar;
}

#pragma mark - UICollectionViewDataSource

// ④ アイテムの総数を返す
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return self.messages.count;
}

#pragma mark - Auto Message

// ⑥ 返信メッセージを受信する (自動)
- (void)receiveAutoMessage
{
    // 1秒後にメッセージを受信する
    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(didFinishMessageTimer:)
                                   userInfo:nil
                                    repeats:NO];
}

- (void)didFinishMessageTimer:(NSTimer*)timer
{
    // 効果音を再生する
    [JSQSystemSoundPlayer jsq_playMessageSentSound];
    // 新しいメッセージデータを追加する
    JSQMessage *message = [JSQMessage messageWithSenderId:@"user2"
                                              displayName:@"underscore"
                                                     text:@"Hello"];
    [self.messages addObject:message];
    // メッセージの受信処理を完了する (画面上にメッセージが表示される)
    [self finishReceivingMessageAnimated:YES];
}

@end

① senderId と senderDisplayName の設定

まず始めに必要になるのが senderId と senderDisplayName です。senderId はメッセージデータの送信者を特定するためのユニークなIDです。一意になればどんな文字列でもOKです。senderDisplayName はチャット画面の表示名です。

② MessageBubble (背景の吹き出し) の設定

MessageBubble はメッセージ本文の背景画像です。メッセージ本文に表示される背景画像は、アイテム毎に collectionView:messageBubbleImageDataForItemAtIndexPath: メソッドで返した JSQMessageBubbleImageDataSource のインスタンスが使われます。送信時と受信時で変更したいので、incomingBubble と outgoingBubble というプロパティとして始めに保持しておき、上記メソッドが呼ばれたときに senderId を比較して出し分けています。ところで、誰が最初にバブルって言ったんでしょうね…。

③ アバター画像の設定

アバター画像はメッセージ毎に、メッセージの横に表示されます。アバター画像は MessageBubble とほぼ同様に実装しており、collectionView:avatarImageDataForItemAtIndexPath: メソッド内で senderId を比較して出し分けています。ちなみにこのメソッドは実装が必須になっているので、アバター画像を必要としない場合でも実装して置く必要があります。

④ メッセージデータの作成

データソースの実装方法は UITableView とほぼ同じなので、難しいことは特にありません。IndexPath に対応するデータを collectionView:messageDataForItemAtIndexPath: で返してあげる必要がある点だけ異なります。

⑤ メッセージの送信

画面上に表示される「Send」ボタンを押下したときに didPressSendButton:withMessageText:senderId:senderDisplayName:date が呼び出されるので、この中でメッセージデータの配列に新しいメッセージデータを追加します。メッセージデータは JSQMessage のインスタンスで作成します。コンストラクタ引数に必要なプロパティが渡されてくるので、とても簡単ですね。メッセージの追加後、最後に finishSendingMessageAnimated: (または finishSendingMessage:) を呼んで処理完了です。バックエンドサーバーを利用する場合、ここで通信処理が挟まることになります。

⑥ メッセージの受信

今回はローカルで完結するように実装したかったので、受信メッセージはそれっぽく見えるように、メッセージの送信処理が行われた後にメッセージデータを追加しています。メッセージの送信時と異なる点は JSQMessage インスタンスの senderId プロパティを送信者用の senderId にしているところです。受信者の senderId と異なっていれば受信したメッセージとして扱われます。最後に finishReceivingMessageAnimated: (または finishReceivingMessage:) で完了です。

動作させてみよう

以上で実装完了です。実行後テキストを入力して「Send」ボタンを押すと、チャットアプリのように動作する様子が確認できると思います。

JSQMessagesViewController

何を問いかけても Hello と返してくれます。何を問いかけても。

まとめ

今回は JSQMessagesViewController の一番簡単な使いかたをまとめてみました。今回実装したチャットアプリはサーバーサイドは特に使っていないのでローカルで完結していますが、最近は mBaaS が大流行していますので、Parse や KiiCloud などのサービス (もちろん AWS も!) を利用してメッセージの保存・取得を行うと良いと思います。

AWS Cloud Roadshow 2017 福岡