mirror of
https://github.com/MessageKit/MessageKit.git
synced 2026-02-06 19:03:19 +00:00
Merge pull request #1444 from amirpirzad/development
Display message time by swiping left
This commit is contained in:
@@ -8,6 +8,7 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa
|
||||
- Fixes missing insets for link preview messages [#1447](https://github.com/MessageKit/MessageKit/pull/1447) by [@bguidolim](https://github.com/bguidolim)
|
||||
|
||||
### Added
|
||||
- Show message time by swiping left over the chat controller. [#1444](https://github.com/MessageKit/MessageKit/pull/1444) by [@amirpirzad](https://github.com/amirpirzad)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -278,7 +278,6 @@ final class AdvancedExampleViewController: ChatViewController {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MessagesDisplayDelegate
|
||||
|
||||
@@ -112,6 +112,8 @@ class ChatViewController: MessagesViewController, MessagesDataSource {
|
||||
|
||||
scrollsToBottomOnKeyboardBeginsEditing = true // default false
|
||||
maintainPositionOnKeyboardFrameChanged = true // default false
|
||||
|
||||
showMessageTimestampOnSwipeLeft = true // default false
|
||||
|
||||
messagesCollectionView.refreshControl = refreshControl
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ struct MessagesView: UIViewControllerRepresentable {
|
||||
messagesVC.messageInputBar.delegate = context.coordinator
|
||||
messagesVC.scrollsToBottomOnKeyboardBeginsEditing = true // default false
|
||||
messagesVC.maintainPositionOnKeyboardFrameChanged = true // default false
|
||||
messagesVC.showMessageTimestampOnSwipeLeft = true // default false
|
||||
|
||||
return messagesVC
|
||||
}
|
||||
@@ -95,6 +96,14 @@ extension MessagesView.Coordinator: MessagesDataSource {
|
||||
let dateString = formatter.string(from: message.sentDate)
|
||||
return NSAttributedString(string: dateString, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)])
|
||||
}
|
||||
|
||||
func messageTimestampLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
|
||||
let sentDate = message.sentDate
|
||||
let sentDateString = MessageKitDateFormatter.shared.string(from: sentDate)
|
||||
let timeLabelFont: UIFont = .boldSystemFont(ofSize: 10)
|
||||
let timeLabelColor: UIColor = .systemGray
|
||||
return NSAttributedString(string: sentDateString, attributes: [NSAttributedString.Key.font: timeLabelFont, NSAttributedString.Key.foregroundColor: timeLabelColor])
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
|
||||
@@ -28,7 +28,7 @@ import InputBarAccessoryView
|
||||
/// A subclass of `UIViewController` with a `MessagesCollectionView` object
|
||||
/// that is used to display conversation interfaces.
|
||||
open class MessagesViewController: UIViewController,
|
||||
UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UIGestureRecognizerDelegate {
|
||||
|
||||
/// The `MessagesCollectionView` managed by the messages view controller object.
|
||||
open var messagesCollectionView = MessagesCollectionView()
|
||||
@@ -56,6 +56,22 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
/// The default value of this property is `false`.
|
||||
open var maintainPositionOnKeyboardFrameChanged: Bool = false
|
||||
|
||||
/// Display the date of message by swiping left.
|
||||
/// The default value of this property is `false`.
|
||||
open var showMessageTimestampOnSwipeLeft: Bool = false {
|
||||
didSet {
|
||||
messagesCollectionView.showMessageTimestampOnSwipeLeft = showMessageTimestampOnSwipeLeft
|
||||
if showMessageTimestampOnSwipeLeft {
|
||||
addPanGesture()
|
||||
} else {
|
||||
removePanGesture()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pan gesture for display the date of message by swiping left.
|
||||
private var panGesture: UIPanGestureRecognizer?
|
||||
|
||||
open override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
@@ -148,6 +164,53 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
|
||||
// MARK: - Methods [Private]
|
||||
|
||||
/// Display time of message by swiping the cell
|
||||
private func addPanGesture() {
|
||||
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
|
||||
guard let panGesture = panGesture else {
|
||||
return
|
||||
}
|
||||
panGesture.delegate = self
|
||||
messagesCollectionView.addGestureRecognizer(panGesture)
|
||||
messagesCollectionView.clipsToBounds = false
|
||||
}
|
||||
|
||||
private func removePanGesture() {
|
||||
guard let panGesture = panGesture else {
|
||||
return
|
||||
}
|
||||
panGesture.delegate = nil
|
||||
self.panGesture = nil
|
||||
messagesCollectionView.removeGestureRecognizer(panGesture)
|
||||
messagesCollectionView.clipsToBounds = true
|
||||
}
|
||||
|
||||
@objc
|
||||
private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
|
||||
guard let parentView = gesture.view else {
|
||||
return
|
||||
}
|
||||
|
||||
switch gesture.state {
|
||||
case .began, .changed:
|
||||
messagesCollectionView.showsVerticalScrollIndicator = false
|
||||
let translation = gesture.translation(in: view)
|
||||
let minX = -(view.frame.size.width * 0.35)
|
||||
let maxX: CGFloat = 0
|
||||
var offsetValue = translation.x
|
||||
offsetValue = max(offsetValue, minX)
|
||||
offsetValue = min(offsetValue, maxX)
|
||||
parentView.frame.origin.x = offsetValue
|
||||
case .ended:
|
||||
messagesCollectionView.showsVerticalScrollIndicator = true
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: .curveEaseOut, animations: {
|
||||
parentView.frame.origin.x = 0
|
||||
}, completion: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func setupDefaults() {
|
||||
extendedLayoutIncludesOpaqueBars = true
|
||||
view.backgroundColor = .collectionViewBackground
|
||||
@@ -420,4 +483,15 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
|
||||
@objc private func clearMemoryCache() {
|
||||
MessageStyle.bubbleImageCache.removeAllObjects()
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate
|
||||
|
||||
/// check pan gesture direction
|
||||
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else {
|
||||
return false
|
||||
}
|
||||
let velocity = panGesture.velocity(in: messagesCollectionView)
|
||||
return abs(velocity.x) > abs(velocity.y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ open class MessageSizeCalculator: CellSizeCalculator {
|
||||
attributes.cellTopLabelSize = cellTopLabelSize(for: message, at: indexPath)
|
||||
attributes.cellTopLabelAlignment = cellTopLabelAlignment(for: message)
|
||||
attributes.cellBottomLabelSize = cellBottomLabelSize(for: message, at: indexPath)
|
||||
attributes.messageTimeLabelSize = messageTimeLabelSize(for: message, at: indexPath)
|
||||
attributes.cellBottomLabelAlignment = cellBottomLabelAlignment(for: message)
|
||||
attributes.messageTopLabelSize = messageTopLabelSize(for: message, at: indexPath)
|
||||
attributes.messageTopLabelAlignment = messageTopLabelAlignment(for: message)
|
||||
@@ -197,7 +198,18 @@ open class MessageSizeCalculator: CellSizeCalculator {
|
||||
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
|
||||
return isFromCurrentSender ? outgoingMessageTopLabelAlignment : incomingMessageTopLabelAlignment
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Message time label
|
||||
|
||||
open func messageTimeLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
let dataSource = messagesLayout.messagesDataSource
|
||||
guard let attributedText = dataSource.messageTimestampLabelAttributedText(for: message, at: indexPath) else {
|
||||
return .zero
|
||||
}
|
||||
let size = attributedText.size()
|
||||
return CGSize(width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
// MARK: - Bottom cell Label
|
||||
|
||||
open func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
|
||||
@@ -50,6 +50,8 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu
|
||||
public var messageBottomLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
|
||||
public var messageBottomLabelSize: CGSize = .zero
|
||||
|
||||
public var messageTimeLabelSize: CGSize = .zero
|
||||
|
||||
public var accessoryViewSize: CGSize = .zero
|
||||
public var accessoryViewPadding: HorizontalEdgeInsets = .zero
|
||||
public var accessoryViewPosition: AccessoryPosition = .messageCenter
|
||||
@@ -74,6 +76,7 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu
|
||||
copy.cellTopLabelSize = cellTopLabelSize
|
||||
copy.cellBottomLabelAlignment = cellBottomLabelAlignment
|
||||
copy.cellBottomLabelSize = cellBottomLabelSize
|
||||
copy.messageTimeLabelSize = messageTimeLabelSize
|
||||
copy.messageTopLabelAlignment = messageTopLabelAlignment
|
||||
copy.messageTopLabelSize = messageTopLabelSize
|
||||
copy.messageBottomLabelAlignment = messageBottomLabelAlignment
|
||||
@@ -100,6 +103,7 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu
|
||||
&& attributes.cellTopLabelSize == cellTopLabelSize
|
||||
&& attributes.cellBottomLabelAlignment == cellBottomLabelAlignment
|
||||
&& attributes.cellBottomLabelSize == cellBottomLabelSize
|
||||
&& attributes.messageTimeLabelSize == messageTimeLabelSize
|
||||
&& attributes.messageTopLabelAlignment == messageTopLabelAlignment
|
||||
&& attributes.messageTopLabelSize == messageTopLabelSize
|
||||
&& attributes.messageBottomLabelAlignment == messageBottomLabelAlignment
|
||||
|
||||
@@ -102,6 +102,18 @@ public protocol MessagesDataSource: AnyObject {
|
||||
///
|
||||
/// The default value returned by this method is `nil`.
|
||||
func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
|
||||
|
||||
/// The attributed text to be used for cell's timestamp label.
|
||||
/// The timestamp label is shown when showMessageTimestampOnSwipeLeft is enabled by swiping left over the chat controller.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The `MessageType` that will be displayed by this cell.
|
||||
/// - indexPath: The `IndexPath` of the cell.
|
||||
/// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
|
||||
///
|
||||
/// The default value returned by this method is `nil`.
|
||||
func messageTimestampLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
|
||||
|
||||
|
||||
/// Custom collectionView cell for message with `custom` message type.
|
||||
///
|
||||
@@ -148,7 +160,20 @@ public extension MessagesDataSource {
|
||||
func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func messageTimestampLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
|
||||
let sentDate = message.sentDate
|
||||
let sentDateString = MessageKitDateFormatter.shared.string(from: sentDate)
|
||||
let timeLabelFont: UIFont = .boldSystemFont(ofSize: 10)
|
||||
let timeLabelColor: UIColor
|
||||
if #available(iOS 13, *) {
|
||||
timeLabelColor = .systemGray
|
||||
} else {
|
||||
timeLabelColor = .darkGray
|
||||
}
|
||||
return NSAttributedString(string: sentDateString, attributes: [NSAttributedString.Key.font: timeLabelFont, NSAttributedString.Key.foregroundColor: timeLabelColor])
|
||||
}
|
||||
|
||||
func customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell {
|
||||
fatalError(MessageKitError.customDataUnresolvedCell)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ open class MessageContentCell: MessageCollectionViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
/// The time label of the messageBubble.
|
||||
open var messageTimestampLabel: InsetLabel = InsetLabel()
|
||||
|
||||
// Should only add customized subviews - don't change accessoryView itself.
|
||||
open var accessoryView: UIView = UIView()
|
||||
|
||||
@@ -95,6 +98,7 @@ open class MessageContentCell: MessageCollectionViewCell {
|
||||
contentView.addSubview(cellBottomLabel)
|
||||
contentView.addSubview(messageContainerView)
|
||||
contentView.addSubview(avatarView)
|
||||
contentView.addSubview(messageTimestampLabel)
|
||||
}
|
||||
|
||||
open override func prepareForReuse() {
|
||||
@@ -103,6 +107,7 @@ open class MessageContentCell: MessageCollectionViewCell {
|
||||
cellBottomLabel.text = nil
|
||||
messageTopLabel.text = nil
|
||||
messageBottomLabel.text = nil
|
||||
messageTimestampLabel.attributedText = nil
|
||||
}
|
||||
|
||||
// MARK: - Configuration
|
||||
@@ -118,6 +123,7 @@ open class MessageContentCell: MessageCollectionViewCell {
|
||||
layoutMessageTopLabel(with: attributes)
|
||||
layoutAvatarView(with: attributes)
|
||||
layoutAccessoryView(with: attributes)
|
||||
layoutTimeLabelView(with: attributes)
|
||||
}
|
||||
|
||||
/// Used to configure the cell.
|
||||
@@ -150,11 +156,13 @@ open class MessageContentCell: MessageCollectionViewCell {
|
||||
let bottomCellLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
|
||||
let topMessageLabelText = dataSource.messageTopLabelAttributedText(for: message, at: indexPath)
|
||||
let bottomMessageLabelText = dataSource.messageBottomLabelAttributedText(for: message, at: indexPath)
|
||||
|
||||
let messageTimestampLabelText = dataSource.messageTimestampLabelAttributedText(for: message, at: indexPath)
|
||||
cellTopLabel.attributedText = topCellLabelText
|
||||
cellBottomLabel.attributedText = bottomCellLabelText
|
||||
messageTopLabel.attributedText = topMessageLabelText
|
||||
messageBottomLabel.attributedText = bottomMessageLabelText
|
||||
messageTimestampLabel.attributedText = messageTimestampLabelText
|
||||
messageTimestampLabel.isHidden = !messagesCollectionView.showMessageTimestampOnSwipeLeft
|
||||
}
|
||||
|
||||
/// Handle tap gesture on contentView and its subviews.
|
||||
@@ -344,4 +352,13 @@ open class MessageContentCell: MessageCollectionViewCell {
|
||||
|
||||
accessoryView.frame = CGRect(origin: origin, size: attributes.accessoryViewSize)
|
||||
}
|
||||
|
||||
/// Positions the message bubble's time label.
|
||||
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
|
||||
open func layoutTimeLabelView(with attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
let paddingLeft: CGFloat = 10
|
||||
let origin = CGPoint(x: contentView.frame.size.width + paddingLeft, y: contentView.frame.size.height * 0.5)
|
||||
let size = CGSize(width: attributes.messageTimeLabelSize.width, height: attributes.messageTimeLabelSize.height)
|
||||
messageTimestampLabel.frame = CGRect(origin: origin, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ open class MessagesCollectionView: UICollectionView {
|
||||
return messagesCollectionViewFlowLayout.isTypingIndicatorViewHidden
|
||||
}
|
||||
|
||||
/// Display the date of message by swiping left.
|
||||
/// The default value of this property is `false`.
|
||||
internal var showMessageTimestampOnSwipeLeft: Bool = false
|
||||
|
||||
private var indexPathForLastItem: IndexPath? {
|
||||
let lastSection = numberOfSections - 1
|
||||
guard lastSection >= 0, numberOfItems(inSection: lastSection) > 0 else { return nil }
|
||||
|
||||
Reference in New Issue
Block a user