メッセージ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をみればだいたい分かると思います。これを作ってくれた作者に感謝!
コメント
コメントを投稿