やりたいこと
- いい感じのチャットのUIを作りたい
- scroll to bottomの動きや、伸縮するキーボードなど、大変そうなのでライブラリを使いたい
- MessageKitを使う
- 用意されている以外のUIコンポーネントを自作して使用したい
作りたいのは下のような画面で、アバターの描画、カスタムセルの作り方などにハマったので書いていきます
画像
トピック
- 基本的な使い方
- アイコン部分の細かい調整
- 自作セルを使って日付のセルを追加する
基本的な使い方
メッセージ部分のカラー設定やマージンの設定については割愛します。
基本的な使い方については下記を参考にさせていただきました。
- MessageKit - README.md
- MessageKitの使いかた(初歩)
- 【Swift】JSQMessagesViewController後継のMessageKitでのチャット画面の作成方法
アイコン部分の細かい調整
メッセージ部分の構造は画像(READMEより抜粋)のようになっていて、赤枠がviewの構成単位になっています。
各Viewを適宜レイアウト調整用のメソッドを呼んだり、Delegateメソッド経由で調整することでレイアウトを組んでいきます。
アイコン部分はAvaterView
と呼ばれており、他のview要素と同じ用に上記のアプローチで細かくレイアウトを設定していくことになります。
今回のアイコン部分の実装では以下を要件とします
- 自分が送るときはアイコンなし
- 相手から来たメッセージはアイコンあり(48 x 48、画像の上辺と吹き出しの上辺を合わせる)
- アイコンには角丸+影をつけたい
自分が送るときはアイコンなし
layout.setMessageOutgoingAvatarSize(.zero)
上記のメソッドからアバターサイズを.zero
で設定します。
自分から送る(出ていく = Outgoing)アバターサイズを設定するメソッド、
という感じの命名になっていて、他のコンポーネントも同じノリです。
ちなみにlayoutプロパティは下記のように、
messageCollectionView
から取得できるcollectionViewLayout
から呼び出すことができます。
(messageCollectionView
とはMessagesViewController
のクラスが保持しているプロパティです)
class ChatViewController: MessagesViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let layout = self.messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout {
// ここでlayoutを取得できる
}
}
}
相手から来たメッセージはアイコンあり
layout.setMessageIncomingAvatarSize(CGSize(width: 48, height: 48))
layout.setMessageIncomingAvatarPosition(AvatarPosition(vertical: .cellTop))
同じノリで、相手から受け取った(送られてくる = Incoming)メッセージも上記のように設定できます。
AvatarPosition
は水平方向、垂直方向に対してenumが定義されていて、
垂直方向のみによるinitと、水平方向と垂直方向によるinitの2種類提供されています。
アイコンには影をつけたい
// MARK: - MessagesLayoutDelegate
// バブルの部分的な描画処理や、アクセサリービューやアバタービューの描画のときに呼ばれるDelegate
// ここでaddSubViewすることでいい感じにレイアウトを組む
extension ChatViewController: MessagesDisplayDelegate {
func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
// 初期化
avatarView.subviews.forEach {
$0.removeFromSuperview()
}
// 相手のみアイコンを描画
if !self.isFromCurrentSender(message: message) {
// subViewにシャドーをつけるためにfalseにしておく
avatarView.clipsToBounds = false
// avatarViewにUserIconViewをaddする
let userIconView = UserIconView()
let avaterSize = avatarView.frame.size
avatarView.addSubview(userIconView)
}
}
}
AvatarViewの画像などの設定は下記のdelegateを通して設定します。
AvatarViewにはopen func set(avatar: Avatar)
のメソッドが公開されており、
このメソッド経由で画像を設定するべきですが、
角丸 + 影を付ける場合にはシャドー付きのviewとcornerRadiusを設定したviewを重ねて表示する必要があるため、
今回はAvaterViewには画像を設定せず、
直接角丸 + 影付きの自作のUserIconViewをaddSubViewすることで実現しました。
また、描画の際に毎回呼ばれるので、セルの再利用で影が再描画され濃くなっていかないように、
初期化してremoveFromSuperview()
をしていく必要があります。
自作セルを使って日付のセルを追加する
基本的なチャットに必要なコンポーネントはMessageKitで提供してくれており、
メッセージ用のview要素も、テキスト用の他に画像や絵文字、音声にも対応したUIが利用できるみたいです。
ですが、やはりどうしても賄えない部分は存在するので、その場合はカスタムセルを作ってなんとかします。
そしてその方法もしっかりドキュメントに記されていて、この方法の通りやればカスタムセルを実装できます。
流れは以下のとおりです
1. UICollectionViewCell
を継承した、カスタムセルを作る
2. MessageSizeCalculator
を継承したサイズ計算用のクラスを作る
3. MessagesCollectionViewFlowLayout
を継承した、レイアウトを決定クラスを作る
4. viewDidLoad
でMessagesCollectionViewFlowLayout
を使ったcollectionViewの初期化と、セルのregisterを行う
1. UICollectionViewCell
を継承した、カスタムセルを作る
final class DateCustomCell: UICollectionViewCell {
func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView, date: String) {
// 描画処理
}
}
2. MessageSizeCalculator
を継承したサイズ計算用のクラスを作る
final class DateMessageSizeCalculator: MessageSizeCalculator {
override func messageContainerSize(for message: MessageType) -> CGSize {
return CGSize(width: 124, height: 24)
}
}
3. MessagesCollectionViewFlowLayout
を継承した、レイアウトを決定クラスを作る
final class DateMessagesFlowLayout: MessagesCollectionViewFlowLayout {
lazy var customMessageSizeCalculator = DateMessageSizeCalculator(layout: self)
override func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
if case .custom = message.kind {
return customMessageSizeCalculator // カスタムの時は独自のcalculatorを使用
}
return super.cellSizeCalculatorForItem(at: indexPath) // その他の時は親のcalculatorを使用
}
}
この処理の中でMessageKind
がカスタムのときに 2. で作成したサイズ計算用クラスを使用するように指定する
4. viewDidLoad
でMessagesCollectionViewFlowLayout
を使ったcollectionViewの初期化と、セルのregisterを行う
final class ChatViewController: MessagesViewController {
// MARK: Life cycle
override func viewDidLoad() {
super.viewDidLoad()
self.messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: DateMessagesFlowLayout()) // カスタムセルの時にも処理できるlayoutクラスをセットする
self.messagesCollectionView.register(DateCustomCell.self) // カスタムセルを登録
}
}
最後に
既にご存知だった方もいるかとは思いますが、
スクラッチだとかなり大変なチャットUIの大部分を提供してくれてるMessageKitむちゃくちゃありがたいです。
若干リサーチしつつ実装する必要は出てくるものの、Repository内のExampleもしっかり作られていて参考にできますし、
2021年現在しっかりメンテされているので、チャットをこれから実装しようという方は検討してはいかがでしょうか。