LoginSignup
4
7

More than 1 year has passed since last update.

MessageViewControllerの画面をタップしてキーボードを閉じる方法(MessageKit)

Last updated at Posted at 2021-06-27

はじめに

どうも@kaneko77です。
今回は業務でMessagekitを使っていたときに画面タップした際にキーボードを閉じる方法がネットに全く情報がなかった為共有したいと思います。
3日かかってこの解に行きついた為、お役に立てましたら是非いいねください😄
テキストフィールドなど適当な画面タップしたらキーボード閉じるの当たり前だと思うのでぜひ実装してみてください。

キーボードを消すにはどうするのか?

以下がキーボードを閉じるコードになります。
resignFirstResponderについてはこちらを参照

self.messageInputBar.inputTextView.resignFirstResponder()

各々のタップイベントを取得していく

次にタップイベントを取得する必要があります。
MessegaKitではタップイベントが専用のコールバックメソッドで用意されています。(セルのみ)

タップイベントの一覧

こちらを対象に各々のタップイベントを説明します。
ヒエラルキーで絞って表示してます。
一応リファレンスも貼っておきます。(リファレンスなんか古い...)
github参照してください

下記のメソッドはMessageCellDelegateを継承する必要があります。

関数名 タップ位置 画像
didTapBackground バックグラウンド
didTapMessage メッセージ
didTapAvatar アバター
didTapMessageBottomLabel メッセージの下部
didTapMessageTopLabel メッセージの上部

これでセルに関してのタッチイベントは網羅しました。

バックグラウンドのタップ

セルのバックグラウンドとは別のバックグラウンドになります。
実はUICollectionViewをチャットは継承していまして、
コレクションのバックグラウンドも叩けるようになる必要があります。
キーボードを表示するときに隙間ができたりセルの数が3個未満だと多分バックグランドが剥き出して表示されるはず..
とういうことでバックグラウンドタップのタップジェスチャーを入れます。

 結論

説明が長いので先に結論を述べます。
普通だったらViewControllerのViewにジェスチャーつけてはい終わりですがそうはいかない理由があります(※後で説明)
MessagesCollectionViewをカスタムで定義してタップジェスチャーつけてキーボードを閉じる機能を実現します。
それでは説明いきます。

説明

先にヒエラルキー
UIColletionViewChatMessagesCollectionViewという名前で登録されています。
ヒエラルキー上で見るとわかりやすいかな。。

まず順番に説明させてください。
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見てて思ったのがライブラリ開発って大変なんだなと思いました。
ここまで見ていただきありがとうございます。
これからは皆さんググれば私の記事に行き着くと思うので、私みたいに時間を食う人がかなり減るはず!!!
皆さんのお役に立てますと幸いです。

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7