【Swift】JSQMessagesViewController後継のMessageKitでのチャット画面の作成方法
はじめに
Swiftでチャットアプリを作ろうと思い、どんなライブラリが主流なのかな?と思って調べていると、これまではJSQMessagesViewControllerが一般的だったらしいですが、すでに開発が終了しており、現在は非推奨となっていることが分かりました。
そして、JSQMessagesViewControllerの後継?のMessageKitなるものを見つけました。
使い方を解説してくれているサイトが少なかったのですが、以下のサイトが非常に分かりやすかったです。
ちなみに全部英語です(笑)
英語がある程度できれば問題なく読めますが、英語ってだけで挫折しそう!って方もいると思うので、そんな方たちのためにこの記事ではMessageKitの使い方をかみ砕いて日本語で説明していこうと思います!
Firebase Tutorial: Real-time ChatやMessageKitのサンプルプロジェクトを合わせて見るとより理解できると思います。
環境
macOS Mojave 10.14.3, XCode 10.2.1, iOS 12.2, Swift 4.2.1, MessageKit 2.0.0
MessageKitのインストール
以下の手順でCocoaPodsを使用してライブラリをインストールします。
$ cd プロジェクト名 $ pod init $ vim Podfile $ pod install
Podfileには以下の内容を記述します。
# Pods for FirebaseChat pod 'MessageKit' pod 'MessageInputBar' // ver2.0.0から必要
スポンサーリンク
MessageKitの使い方
MessageKitはUICollectionViewController
の強化版みたいな感じなので、UICollectionViewController
の使い方が分かっていれば割と簡単です。
MessagesViewController
クラスを継承するだけでチャット画面を作れます。
後は必要なプロトコルを実装していくって感じです。
メッセージはMessageType
に準拠したMessage
という構造体を自分で用意する必要があるんですが、これはMessageKitのサンプルプロジェクト内のMockMessage
を利用すれば問題ありません。
class ChatViewController: MessagesViewController { private var messages: [Message] = [] override func viewDidLoad() { super.viewDidLoad() messagesCollectionView.messagesDataSource = self messagesCollectionView.messageCellDelegate = self messagesCollectionView.messagesLayoutDelegate = self messagesCollectionView.messagesDisplayDelegate = self messagesCollectionView.messageCellDelegate = self messageInputBar.delegate = self } }
メッセージの送信やアバターの設定といったカスタマイズは以下の6つのプロトコルを使って行います。
- MessagesDataSource
- MessageInputBarDelegate
- MessagesDisplayDelegate
- MessagesLayoutDelegate
- MessageCellDelegate
- MessageLabelDelegate
それぞれについて簡単に説明していきます。
設定項目は多いですが、テキストメッセージの送受信さえとりあえずできればOKという場合は、 MessagesDataSource
と MessageInputBarDelegate
のみ実装すれば大丈夫です。
MessagesDataSource
メッセージの送信者や画面に表示するメッセージの数などの設定ができます。
全部で9つのメソッドがありますが、必ず実装しないといけないのはcurrentSender
、messageForItem
, numberOfSections
の3つです。
~AttributedText
ではメッセージやセルの上下に表示する文字列を設定できます。
extension ChatViewController: MessagesDataSource { // idとdisplayNameをプロパティに持つ構造体Senderを返す func currentSender() -> Sender { return Sender(id: "1", displayName: "Tom") } func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int { return messages.count } func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType { return messages[indexPath.section] } func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { let name = message.sender.displayName return NSAttributedString( string: name, attributes: [ .font: UIFont.preferredFont(forTextStyle: .caption1), .foregroundColor: UIColor(white: 0.3, alpha: 1) ] ) } }
MessageInputBarDelegate
メッセージ入力バーに関する処理を実装するものです。
ほぼ確実に実装することになるのは、送信ボタンを押した時の処理を表すmessageInputBar
メソッドだと思います。
以下の例は、送信ボタンを押すと、入力された文字列からMessageオブジェクトを作成して、メッセージリストに追加し、メッセージ入力バーを空にして、画面の下までスクロールするというものです。
insertMessage(message)
もしくは、それに値するメソッドは自分で作る必要があります。
そのメソッドの中でメッセージデータをローカルに保存するなり、データベースに保存するなりします。
firebaseを利用する場合の処理と、チャット画面全体をリロードして、送信したメッセージを表示する処理もここで行います。
extension ChatViewController: MessageInputBarDelegate { func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) { let message = Message(text: text, sender: currentSender(), messageId: UUID().uuidString, date: Date()) insertMessage(message) inputBar.inputTextView.text = String() messagesCollectionView.scrollToBottom(animated: true) } }
MessagesDisplayDelegate
メッセージの表示形式やデザインを決めるものです。
テキストメッセージのみに反映される設定や全てのメッセージに反映される設定等いろいろあるので、MessageKitのサンプルプロジェクトを参考にするといいと思います。
以下の例では、相手のメッセージが自分のメッセージかに応じてメッセージの背景色を変えて、メッセージスタイルを吹き出しにしています。
isFromCurrentSender()
がそのメッセージが自分が送信したのもかどうかを判定するメソッドです。
.primary
と.incomingMessage
は自作の色なので、ここは各自好きな色にしてください。
extension ChatViewController: MessagesDisplayDelegate { func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? .primary : .incomingMessage } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft return .bubbleTail(corner, .curved) } }
MessagesLayoutDelegate
このプロトコルはメッセージ周辺のラベルのレイアウトを決めるものです。
メッセージ周辺のラベルというのは、メッセージ付近に表示される時間や既読、日付といったものです。
それらの位置や大きさはここで設定できます。
メッセージの下に余白を作るといったこともここで行います。
extension ChatViewController: MessagesLayoutDelegate { func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 18 } func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 20 } func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 16 } }
MessageCellDelegate
チャット画面で様々なものをタップした時の処理を実装できます。
以下は、アバター(自分のプロフ画像が表示される所)とメッセージがタップされた時の処理の例です。
アバターとメッセージをタップするとコンソールにそれぞれ文字列が出力されます。
extension ChatViewController: MessageCellDelegate { func didTapAvatar(in cell: MessageCollectionViewCell) { print("Avatar tapped") } func didTapMessage(in cell: MessageCollectionViewCell) { print("Message tapped") } }
MessageLabelDelegate
メッセージ中の電話番号や住所、URLなどをタップした時の処理を実装できます。
URLをタップしたらブラウザを開く、住所をタップしたらマップを起動するといった処理はここで実装します。
以下は例なので、コンソールに文字列を出力するだけです。
extension ChatViewController: MessageLabelDelegate { func didSelectAddress(_ addressComponents: [String: String]) { print("Address Selected: \(addressComponents)") } func didSelectPhoneNumber(_ phoneNumber: String) { print("Phone Number Selected: \(phoneNumber)") } func didSelectURL(_ url: URL) { print("URL Selected: \(url)") } }
おわりに
さらに詳しく知りたい方はMessageKit Referenceをご覧ください。
今回の内容だけだとチャットアプリにはならないので、次はFirebaseとも組み合わせた実装例についても記事を書きたいと思います!