メッセージUIライブラリMessageKitの紹介
はじめに
ねこすたでは投稿された画像、動画にコメントをすることができます。そのコメント欄はLINE風のUIにしているのですが、そこで採用したライブラリがJSQMessagesViewControllerでした。
しかし、そのライブラリが去年(2017/07)にDeprecatedになってしまいました。作成者理由
しばらくはJSQMessagesViewControllerを利用していましたが、pod install,updateするたびにDeprecatedが表示される始末。。最近になってコメントのところをいろいろ改修する必要が出てきたのでいい機会なのでライブラリを変更することにしました。
JSQMessagesViewControllerのissueでちょうどよさそうなライブラリを発見。評価も良さそう
Exampleを動かして良さそうなので採用することにしました。
使い方
インストールはREADME参照そして、、使い方がどこにも書いてない!!
Exampleがあるのでそれを見ながら実装することに。
まずはMessagesViewControllerを継承したControllerを作成。
class ConversationViewController: MessagesViewController { var messageList: [MockMessage] = [] // 各メッセージ内容を格納したList
継承したクラスにmessagesCollectionViewという変数があるのでこいつが、UIのcollectionViewになります。
各delegateを設定
override func viewDidLoad() { super.viewDidLoad() ... messagesCollectionView.messagesDataSource = self messagesCollectionView.messagesLayoutDelegate = self messagesCollectionView.messagesDisplayDelegate = self messagesCollectionView.messageCellDelegate = self
inputBarのdelegateも設定
messageInputBar.delegate = self
メッセージのDataSourceを設定
extension CommentViewController: MessagesDataSource { // ユーザー(利用者側)の情報を返す func currentSender() -> Sender { return Sender(id: 一意になるID, displayName: 表示する名前) } 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? { // この場合3つ毎に時間日付表示 if indexPath.section % 3 == 0 { return NSAttributedString(string: MessageKitDateFormatter.shared.string(from: message.sentDate), attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 10), NSAttributedStringKey.foregroundColor: UIColor.darkGray]) } return nil } func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { let name = message.sender.displayName return NSAttributedString(string: name, attributes: [NSAttributedStringKey.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: [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: .caption2)]) } }
メッセージ表示デリゲート
extension ConversationViewController: MessagesDisplayDelegate { // MARK: - Text Messages func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? .white : .darkText } func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedStringKey: Any] { return MessageLabel.defaultAttributes } func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType] { return [.url, .address, .phoneNumber, .date, .transitInformation] } // MARK: - All Messages func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1) : UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1) } 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) // let configurationClosure = { (view: MessageContainerView) in} // return .custom(configurationClosure) } // アバター情報を設定 func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) { // 画像を設定する場合はUIImageを設定 // URLから画像設定する場合なんかは、AlamofireImage(https://github.com/Alamofire/AlamofireImage)なんかを使うといい let avatar = Avatar(image: nil, initials: message.sender.displayName) avatarView.set(avatar: avatar) } // MARK: - Location Messages func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? { let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil) let pinImage = #imageLiteral(resourceName: "pin") annotationView.image = pinImage annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2) return annotationView } func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? { return { view in view.layer.transform = CATransform3DMakeScale(0, 0, 0) view.alpha = 0.0 UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: { view.layer.transform = CATransform3DIdentity view.alpha = 1.0 }, completion: nil) } } func snapshotOptionsForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LocationMessageSnapshotOptions { return LocationMessageSnapshotOptions() } }
メッセージレイアウトデリゲート
ラベルの高さなど
extension ConversationViewController: MessagesLayoutDelegate { func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { if indexPath.section % 3 == 0 { return 10 } return 0 } func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 16 } func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 16 } }
メッセージセルデリゲート
ボタン押された処理などを実装
extension ConversationViewController: MessageCellDelegate { func didTapAvatar(in cell: MessageCollectionViewCell) { print("Avatar tapped") } func didTapMessage(in cell: MessageCollectionViewCell) { print("Message tapped") } func didTapCellTopLabel(in cell: MessageCollectionViewCell) { print("Top cell label tapped") } func didTapMessageTopLabel(in cell: MessageCollectionViewCell) { print("Top message label tapped") } func didTapMessageBottomLabel(in cell: MessageCollectionViewCell) { print("Bottom label tapped") } }
インプットバーのデリゲート
入力した後などの処理
extension ConversationViewController: MessageInputBarDelegate { func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) { // Each NSTextAttachment that contains an image will count as one empty character in the text: String for component in inputBar.inputTextView.components { if let image = component as? UIImage { let imageMessage = MockMessage(image: image, sender: currentSender(), messageId: UUID().uuidString, date: Date()) messageList.append(imageMessage) messagesCollectionView.insertSections([messageList.count - 1]) } else if let text = component as? String { let attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 15), .foregroundColor: UIColor.blue]) let message = MockMessage(attributedText: attributedText, sender: currentSender(), messageId: UUID().uuidString, date: Date()) messageList.append(message) messagesCollectionView.insertSections([messageList.count - 1]) } } inputBar.inputTextView.text = String() messagesCollectionView.scrollToBottom() } }
メッセージ内容を格納するmodel
MockMessage.swift
import Foundation import CoreLocation import MessageKit private struct MockLocationItem: LocationItem { var location: CLLocation var size: CGSize init(location: CLLocation) { self.location = location self.size = CGSize(width: 240, height: 240) } } private struct MockMediaItem: MediaItem { var url: URL? var image: UIImage? var placeholderImage: UIImage var size: CGSize init(image: UIImage) { self.image = image self.size = CGSize(width: 240, height: 240) self.placeholderImage = UIImage() } } internal struct MockMessage: MessageType { var messageId: String var sender: Sender var sentDate: Date var kind: MessageKind private init(kind: MessageKind, sender: Sender, messageId: String, date: Date) { self.kind = kind self.sender = sender self.messageId = messageId self.sentDate = date } init(text: String, sender: Sender, messageId: String, date: Date) { self.init(kind: .text(text), sender: sender, messageId: messageId, date: date) } init(attributedText: NSAttributedString, sender: Sender, messageId: String, date: Date) { self.init(kind: .attributedText(attributedText), sender: sender, messageId: messageId, date: date) } init(image: UIImage, sender: Sender, messageId: String, date: Date) { let mediaItem = MockMediaItem(image: image) self.init(kind: .photo(mediaItem), sender: sender, messageId: messageId, date: date) } init(thumbnail: UIImage, sender: Sender, messageId: String, date: Date) { let mediaItem = MockMediaItem(image: thumbnail) self.init(kind: .video(mediaItem), sender: sender, messageId: messageId, date: date) } init(location: CLLocation, sender: Sender, messageId: String, date: Date) { let locationItem = MockLocationItem(location: location) self.init(kind: .location(locationItem), sender: sender, messageId: messageId, date: date) } init(emoji: String, sender: Sender, messageId: String, date: Date) { self.init(kind: .emoji(emoji), sender: sender, messageId: messageId, date: date) } }
後はメッセージを受信した際に登録する処理
// データが複数ある場合などは、loopで回してモデルに入れたあと、listに格納。最後にreloadData()する let message = MockMessage(text: メッセージ内容, sender: Sender(id: 一意のuser id, displayName: ユーザー名, messageId: 一意のメッセージID, date: コメントの日付) self.messages.append(message) self.messagesCollectionView.insertSections([self.messages.count - 1]) self.messagesCollectionView.reloadData()
さいごに
オフィシャルのREADMEに使い方が書いてないのがあれですが、Exampleをみればだいたい分かると思います。これを作ってくれた作者に感謝!
コメント
コメントを投稿