はじめに
どうも@kaneko77です。
今回は業務でMessagekitを使っていたときに画面タップした際にキーボードを閉じる方法がネットに全く情報がなかった為共有したいと思います。
3日かかってこの解に行きついた為、お役に立てましたら是非いいねください😄
テキストフィールドなど適当な画面タップしたらキーボード閉じるの当たり前だと思うのでぜひ実装してみてください。
キーボードを消すにはどうするのか?
以下がキーボードを閉じるコードになります。
resignFirstResponder
についてはこちらを参照
self.messageInputBar.inputTextView.resignFirstResponder()
各々のタップイベントを取得していく
次にタップイベントを取得する必要があります。
MessegaKitではタップイベントが専用のコールバックメソッドで用意されています。(セルのみ)
タップイベントの一覧
こちらを対象に各々のタップイベントを説明します。
ヒエラルキーで絞って表示してます。
一応リファレンスも貼っておきます。(リファレンスなんか古い...)
github参照してください
下記のメソッドはMessageCellDelegate
を継承する必要があります。
関数名 | タップ位置 | 画像 |
---|---|---|
didTapBackground | バックグラウンド | |
didTapMessage | メッセージ | |
didTapAvatar | アバター | |
didTapMessageBottomLabel | メッセージの下部 | |
didTapMessageTopLabel | メッセージの上部 |
バックグラウンドのタップ
セルのバックグラウンドとは別のバックグラウンドになります。
実はUICollectionView
をチャットは継承していまして、
コレクションのバックグラウンドも叩けるようになる必要があります。
キーボードを表示するときに隙間ができたりセルの数が3個未満だと多分バックグランドが剥き出して表示されるはず..
とういうことでバックグラウンドタップのタップジェスチャーを入れます。
結論
説明が長いので先に結論を述べます。
普通だったらViewControllerのViewにジェスチャーつけてはい終わりですがそうはいかない理由があります(※後で説明)
MessagesCollectionView
をカスタムで定義してタップジェスチャーつけてキーボードを閉じる機能を実現します。
それでは説明いきます。
説明
先にヒエラルキー
UIColletionView
がChatMessagesCollectionView
という名前で登録されています。
ヒエラルキー上で見るとわかりやすいかな。。
まず順番に説明させてください。
MessagesViewControllerを継承するとmessagesCollectionView
が使えるようになりそいつにセルを貼り付けてChatでのゴニョゴニョが可能になります。
class ChatViewController: MessagesViewController {
override func viewDidLoad() {
super.viewDidLoad()
messagesCollectionView
}
}
messagesCollectionView
を見ると、、、はい先ほどヒエラルキーで見たChatMessagesCollectionView
が出てきました。
それでmessagesCollectionView
はすでにジェスチャー機能がデフォルトで付けられているため最初にお話ししたカスタムで改めてジェスチャー機能がついているクラスを作り
messagesCollectionView
に代入してあげる必要があります。
コード
以下がカスタムクラスです。
こちらにGesture
つきました。
こちら参考にしました。
protocol MessagesCollectionViewDelegate: AnyObject {
func didTap()
}
class ChatMessagesCollectionView: MessagesCollectionView {
weak var collectionDelegate: MessagesCollectionViewDelegate?
override func handleTapGesture(_ gesture: UIGestureRecognizer) {
super.handleTapGesture(gesture)
collectionDelegate?.didTap()
}
}
先ほどのクラスを...
代入します。
override func loadView() {
super.loadView()
let collectionView = ChatMessagesCollectionView()
collectionView.collectionDelegate = self
messagesCollectionView = collectionView
}
これでバックグラウンドタップが実現できるようになりました。
全コード貼っちゃおう✨
こちらに肉付けして書いてます。
import UIKit
import MessageKit
import InputBarAccessoryView
class ChatViewController: MessagesViewController {
var messageList: [MockMessage] = [] {
didSet {
// messagesCollectionViewをリロード
self.messagesCollectionView.reloadData()
// 一番下までスクロールする
self.messagesCollectionView.scrollToLastItem()
}
}
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.locale = Locale(identifier: "ja_JP")
return formatter
}()
override func loadView() {
super.loadView()
let collectionView = ChatMessagesCollectionView()
collectionView.collectionDelegate = self
messagesCollectionView = collectionView
}
override func viewDidLoad() {
super.viewDidLoad()
// モックデータを取得
DispatchQueue.main.async { self.messageList = MockMessage.getMessages() }
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
messagesCollectionView.messageCellDelegate = self
messageInputBar.delegate = self
setupInput()
setupButton()
// 背景の色を指定
messagesCollectionView.backgroundColor = .white
// メッセージ入力時に一番下までスクロール
scrollsToLastItemOnKeyboardBeginsEditing = true
maintainPositionOnKeyboardFrameChanged = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
private func setupInput(){
// プレースホルダーの指定
messageInputBar.inputTextView.placeholder = "入力"
// 入力欄のカーソルの色を指定
messageInputBar.inputTextView.tintColor = .red
// 入力欄の色を指定
messageInputBar.inputTextView.backgroundColor = .gray
}
private func setupButton(){
// ボタンの変更
messageInputBar.sendButton.title = "送信"
// 送信ボタンの色を指定
messageInputBar.sendButton.tintColor = .lightGray
}
}
// MARK: - MessagesDataSource
extension ChatViewController: MessagesDataSource {
func currentSender() -> SenderType {
return userType.me.data
}
func otherSender() -> SenderType {
return userType.you.data
}
func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int {
return messageList.count
}
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
return messageList[indexPath.section]
}
// メッセージの上に文字を表示
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
if indexPath.section % 3 == 0 {
return NSAttributedString(
string: MessageKitDateFormatter.shared.string(from: message.sentDate),
attributes: [
.font: UIFont.boldSystemFont(ofSize: 10),
.foregroundColor: UIColor.darkGray
]
)
}
return nil
}
// メッセージの上に文字を表示(名前)
func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let name = message.sender.displayName
return NSAttributedString(string: name, attributes: [.font: UIFont.preferredFont(forTextStyle: .caption1)])
}
// メッセージの下に文字を表示(日付)
func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let dateString = formatter.string(from: message.sentDate)
return NSAttributedString(string: dateString, attributes: [.font: UIFont.preferredFont(forTextStyle: .caption2)])
}
}
// MARK: - MessagesDisplayDelegate
extension ChatViewController: MessagesDisplayDelegate {
// メッセージの色を変更(デフォルトは自分:白、相手:黒)
func textColor(
for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView
) -> UIColor {
isFromCurrentSender(message: message) ? .white : .darkText
}
// メッセージの背景色を変更している(デフォルトは自分:緑、相手:グレー)
func backgroundColor(
for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView
) -> UIColor {
isFromCurrentSender(message: message) ? .darkGray : .cyan
}
// メッセージの枠にしっぽを付ける
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)
}
// アイコンをセット
func configureAvatarView(
_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView
) {
avatarView.set( avatar: Avatar(initials: message.sender.senderId == "001" ? "😊" : "🥳") )
}
}
// 各ラベルの高さを設定(デフォルト0なので必須)
// MARK: - MessagesLayoutDelegate
extension ChatViewController: MessagesLayoutDelegate {
func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
indexPath.section % 3 == 0 ? 10 : 0
}
func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
16
}
func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
16
}
}
// MARK: - MessageCellDelegate
extension ChatViewController: MessageCellDelegate {
//MARK: - Cellのバックグラウンドをタップした時の処理
func didTapBackground(in cell: MessageCollectionViewCell) {
print("バックグラウンドタップ")
closeKeyboard()
}
//MARK: - メッセージをタップした時の処理
func didTapMessage(in cell: MessageCollectionViewCell) {
print("メッセージタップ")
closeKeyboard()
}
//MARK: - アバターをタップした時の処理
func didTapAvatar(in cell: MessageCollectionViewCell) {
print("アバタータップ")
closeKeyboard()
}
//MARK: - メッセージ上部をタップした時の処理
func didTapMessageTopLabel(in cell: MessageCollectionViewCell) {
print("メッセージ上部タップ")
closeKeyboard()
}
//MARK: - メッセージ下部をタップした時の処理
func didTapMessageBottomLabel(in cell: MessageCollectionViewCell) {
print("メッセージ下部タップ")
closeKeyboard()
}
}
// MARK: - InputBarAccessoryViewDelegate
extension ChatViewController: InputBarAccessoryViewDelegate {
// 送信ボタンをタップした時の挙動
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
let attributedText = NSAttributedString(
string: text, attributes: [.font: UIFont.systemFont(ofSize: 15), .foregroundColor: UIColor.white])
let message = MockMessage(attributedText: attributedText, sender: currentSender(), messageId: UUID().uuidString, date: Date())
self.messageList.append(message)
self.messageInputBar.inputTextView.text = String()
self.messageInputBar.invalidatePlugins()
self.messagesCollectionView.scrollToLastItem()
}
}
extension ChatViewController {
func closeKeyboard(){
self.messageInputBar.inputTextView.resignFirstResponder()
self.messagesCollectionView.scrollToLastItem()
}
}
extension ChatViewController: MessagesCollectionViewDelegate {
func didTap() {
closeKeyboard()
}
}
protocol MessagesCollectionViewDelegate: AnyObject {
func didTap()
}
class ChatMessagesCollectionView: MessagesCollectionView {
weak var collectionDelegate: MessagesCollectionViewDelegate?
override func handleTapGesture(_ gesture: UIGestureRecognizer) {
super.handleTapGesture(gesture)
collectionDelegate?.didTap()
}
}
終わりに
今回はめちゃくちゃ詰まりまくった世の中にあって当たり前の機能、画面適当タップでキーボードをMessageKitでやる方法でした。
デフォルトのジェスチャー機能を消してほしいと切実に実装中思ってました。
ずっとIssue見てて思ったのがライブラリ開発って大変なんだなと思いました。
ここまで見ていただきありがとうございます。
これからは皆さんググれば私の記事に行き着くと思うので、私みたいに時間を食う人がかなり減るはず!!!
皆さんのお役に立てますと幸いです。