mirror of
https://github.com/MessageKit/MessageKit.git
synced 2026-02-06 19:03:19 +00:00
Add improved MessagesCollectionViewFlowLayout
This commit is contained in:
@@ -34,6 +34,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
emojiLabelFont = messageLabelFont.withSize(2 * messageLabelFont.pointSize)
|
||||
}
|
||||
}
|
||||
|
||||
private var emojiLabelFont: UIFont
|
||||
|
||||
open var avatarAlwaysLeading: Bool {
|
||||
@@ -52,8 +53,31 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var messagesCollectionView: MessagesCollectionView? {
|
||||
return collectionView as? MessagesCollectionView
|
||||
open var attributesCacheMaxSize: Int = 500
|
||||
|
||||
typealias MessageID = String
|
||||
|
||||
fileprivate var intermediateAttributesCache: [MessageID: MessageIntermediateLayoutAttributes] = [:]
|
||||
|
||||
fileprivate var messagesCollectionView: MessagesCollectionView {
|
||||
guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
|
||||
fatalError("MessagesCollectionViewFlowLayout is being used on a foreign type.")
|
||||
}
|
||||
return messagesCollectionView
|
||||
}
|
||||
|
||||
fileprivate var messagesDataSource: MessagesDataSource {
|
||||
guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
|
||||
fatalError("MessagesDataSource has not been set.")
|
||||
}
|
||||
return messagesDataSource
|
||||
}
|
||||
|
||||
fileprivate var messagesLayoutDelegate: MessagesLayoutDelegate {
|
||||
guard let messagesLayoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
|
||||
fatalError("MessagesLayoutDeleagte has not been set.")
|
||||
}
|
||||
return messagesLayoutDelegate
|
||||
}
|
||||
|
||||
fileprivate var itemWidth: CGFloat {
|
||||
@@ -115,52 +139,44 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
|
||||
private func configure(attributes: MessagesCollectionViewLayoutAttributes) {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return }
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else { return }
|
||||
let intermediateAttributes = messageIntermediateLayoutAttributes(for: attributes.indexPath)
|
||||
|
||||
let indexPath = attributes.indexPath
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
let messagePadding = messageContainerPadding(for: message, at: indexPath)
|
||||
let messageInsets = messageLabelInsets(for: message, at: indexPath)
|
||||
let topLabelInsets = cellTopLabelInsets(for: message, at: indexPath)
|
||||
let bottomLabelInsets = cellBottomLabelInsets(for: message, at: indexPath)
|
||||
let messageOrigin = messageContainerOrigin(for: intermediateAttributes, and: attributes.frame)
|
||||
attributes.messageContainerFrame = CGRect(origin: messageOrigin, size: intermediateAttributes.messageContainerSize)
|
||||
|
||||
// First we set all the sizes so we can pass the attributes object around to calculate the origins
|
||||
attributes.messageContainerFrame = CGRect(origin: .zero, size: messageContainerSize(for: message, at: indexPath))
|
||||
attributes.cellTopLabelFrame = CGRect(origin: .zero, size: cellTopLabelSize(for: message, at: indexPath))
|
||||
attributes.cellBottomLabelFrame = CGRect(origin: .zero, size: cellBottomLabelSize(for: message, at: indexPath))
|
||||
attributes.avatarFrame = CGRect(origin: .zero, size: avatarSize(for: message, at: indexPath))
|
||||
let avaOrigin = avatarOrigin(for: intermediateAttributes, and: attributes.frame)
|
||||
attributes.avatarFrame = CGRect(origin: avaOrigin, size: intermediateAttributes.avatarSize)
|
||||
|
||||
switch message.data {
|
||||
let topLabelOrigin = cellTopLabelOrigin(for: intermediateAttributes, and: attributes.frame)
|
||||
attributes.cellTopLabelFrame = CGRect(origin: topLabelOrigin, size: intermediateAttributes.cellTopLabelSize)
|
||||
|
||||
let bottomLabelOrigin = cellBottomLabelOrigin(for: intermediateAttributes, and: attributes.frame)
|
||||
attributes.cellBottomLabelFrame = CGRect(origin: bottomLabelOrigin, size: intermediateAttributes.cellBottomLabelSize)
|
||||
|
||||
switch intermediateAttributes.message.data {
|
||||
case .emoji:
|
||||
attributes.messageLabelFont = emojiLabelFont
|
||||
default:
|
||||
attributes.messageLabelFont = messageLabelFont
|
||||
}
|
||||
|
||||
attributes.messagePadding = messagePadding
|
||||
attributes.messageLabelInsets = messageInsets
|
||||
attributes.cellTopLabelInsets = topLabelInsets
|
||||
attributes.cellBottomLabelInsets = bottomLabelInsets
|
||||
attributes.avatarHorizontalAlignment = avatarHorizontalAlignment(for: message)
|
||||
|
||||
let layoutDelegate = messagesCollectionView.messagesLayoutDelegate
|
||||
let avatarAlignment = layoutDelegate?.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom
|
||||
|
||||
// Now we set the origins for the frames using the attributes object that contains the calculated sizes
|
||||
attributes.messageContainerFrame.origin = messageContainerOrigin(for: attributes)
|
||||
attributes.cellTopLabelFrame.origin = cellTopLabelOrigin(for: message, and: attributes)
|
||||
attributes.cellBottomLabelFrame.origin = cellBottomLabelOrigin(for: message, and: attributes)
|
||||
attributes.avatarFrame.origin = avatarOrigin(for: attributes, with: avatarAlignment)
|
||||
attributes.avatarHorizontalAlignment = intermediateAttributes.avatarHorizontal
|
||||
attributes.messagePadding = intermediateAttributes.messageContainerPadding
|
||||
attributes.messageLabelInsets = intermediateAttributes.messageLabelInsets
|
||||
attributes.cellTopLabelInsets = intermediateAttributes.cellTopLabelAlignment.insets
|
||||
attributes.cellBottomLabelInsets = intermediateAttributes.cellBottomLabelAlignment.insets
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Invalidation Context
|
||||
|
||||
open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
||||
|
||||
return collectionView?.bounds.width != newBounds.width || collectionView?.bounds.height != newBounds.height
|
||||
|
||||
if collectionView?.bounds.width != newBounds.width {
|
||||
intermediateAttributesCache = [:]
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
open override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
|
||||
@@ -170,6 +186,10 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
return flowLayoutContext
|
||||
}
|
||||
|
||||
public func clearCachedAttributes(for message: MessageType) {
|
||||
intermediateAttributesCache.removeValue(forKey: message.messageId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MessagesCollectionViewFlowLayout {
|
||||
@@ -182,62 +202,9 @@ extension MessagesCollectionViewFlowLayout {
|
||||
if avatarAlwaysTrailing { return .cellTrailing }
|
||||
if avatarAlwaysLeading { return .cellLeading }
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .cellLeading }
|
||||
guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .cellLeading }
|
||||
|
||||
return messagesDataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading
|
||||
}
|
||||
|
||||
/// The size of the avatar view.
|
||||
fileprivate func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
|
||||
return layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
/// The origin for the avatar view.
|
||||
fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarAlignment: AvatarAlignment) -> CGPoint {
|
||||
|
||||
var origin = CGPoint.zero
|
||||
|
||||
let contentWidth = attributes.frame.size.width
|
||||
let avatarWidth = attributes.avatarFrame.size.width
|
||||
|
||||
switch attributes.avatarHorizontalAlignment {
|
||||
case .cellLeading:
|
||||
origin.x = 0
|
||||
case .cellTrailing:
|
||||
origin.x = contentWidth - avatarWidth
|
||||
}
|
||||
|
||||
let contentHeight = attributes.frame.height
|
||||
let avatarHeight = attributes.avatarFrame.height
|
||||
let cellTopLabelHeight = attributes.cellTopLabelFrame.height
|
||||
let cellBottomLabelHeight = attributes.cellBottomLabelFrame.height
|
||||
let messageContainerHeight = attributes.messageContainerFrame.height
|
||||
let messagePadding = attributes.messagePadding
|
||||
|
||||
switch avatarAlignment {
|
||||
case .cellTop:
|
||||
origin.y = 0
|
||||
case .cellBottom:
|
||||
origin.y = contentHeight - avatarHeight
|
||||
case .messageTop:
|
||||
origin.y = cellTopLabelHeight + messagePadding.top
|
||||
case .messageBottom:
|
||||
origin.y = contentHeight - avatarHeight - cellBottomLabelHeight - messagePadding.bottom
|
||||
case .messageCenter:
|
||||
let messageMidY = messageContainerHeight / 2
|
||||
let avatarMidY = avatarHeight / 2
|
||||
origin.y = cellTopLabelHeight + messagePadding.top + messageMidY - avatarMidY
|
||||
}
|
||||
|
||||
return origin
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Label Size Calculations
|
||||
|
||||
/// The size required fit a NSAttributedString considering a constrained max width.
|
||||
@@ -264,354 +231,403 @@ extension MessagesCollectionViewFlowLayout {
|
||||
return CGSize(width: finalWidth, height: finalHeight)
|
||||
}
|
||||
|
||||
// MARK: - Message Container Calculations
|
||||
// MARK: - Cell Sizing
|
||||
|
||||
fileprivate func messageContainerPadding(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
return layoutDelegate.messagePadding(for: message, at: indexPath, in: messagesCollectionView)
|
||||
/// The size for the cell considering all cell contents.
|
||||
open func sizeForItem(at indexPath: IndexPath) -> CGSize {
|
||||
let attributes = messageIntermediateLayoutAttributes(for: indexPath)
|
||||
return CGSize(width: itemWidth, height: attributes.itemHeight)
|
||||
}
|
||||
|
||||
/// The insets for the text of the MessageLabel
|
||||
private func messageLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
return layoutDelegate.messageLabelInset(for: message, at: indexPath, in: messagesCollectionView)
|
||||
func messageIntermediateLayoutAttributes(for indexPath: IndexPath) -> MessageIntermediateLayoutAttributes {
|
||||
|
||||
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
|
||||
if let intermediateAttributes = intermediateAttributesCache[message.messageId] {
|
||||
return intermediateAttributes
|
||||
} else {
|
||||
let newAttributes = createMessageIntermediateLayoutAttributes(for: message, at: indexPath)
|
||||
|
||||
if intermediateAttributesCache.count < attributesCacheMaxSize {
|
||||
intermediateAttributesCache[message.messageId] = newAttributes
|
||||
}
|
||||
return newAttributes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// The max width available for the message container.
|
||||
private func messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
func createMessageIntermediateLayoutAttributes(for message: MessageType, at indexPath: IndexPath) -> MessageIntermediateLayoutAttributes {
|
||||
|
||||
let avatarWidth = avatarSize(for: message, at: indexPath).width
|
||||
let messagePadding = messageContainerPadding(for: message, at: indexPath)
|
||||
let messageInsets = messageLabelInsets(for: message, at: indexPath)
|
||||
let attributes = MessageIntermediateLayoutAttributes(message: message, indexPath: indexPath)
|
||||
|
||||
let messageHorizontalPadding = messagePadding.left + messagePadding.right
|
||||
let messageHorizontalInsets = messageInsets.left + messageInsets.right
|
||||
// None of these are dependent on other attributes
|
||||
attributes.avatarVertical = avatarVerticalAlignment(for: attributes)
|
||||
attributes.avatarHorizontal = avatarHorizontalAlignment(for: attributes)
|
||||
attributes.avatarSize = avatarSize(for: attributes)
|
||||
attributes.messageContainerPadding = messageContainerPadding(for: attributes)
|
||||
attributes.messageLabelInsets = messageLabelInsets(for: attributes)
|
||||
|
||||
let availableWidth = itemWidth - avatarWidth - messageHorizontalPadding - messageHorizontalInsets
|
||||
// MessageContainerView
|
||||
attributes.messageContainerMaxWidth = messageContainerMaxWidth(for: attributes)
|
||||
attributes.messageContainerSize = messageContainerSize(for: attributes)
|
||||
|
||||
return availableWidth
|
||||
// Cell Bottom Label
|
||||
attributes.cellBottomLabelText = cellBottomLabelText(for: attributes) // little concerned about storing text here TODO
|
||||
attributes.cellBottomLabelAlignment = cellBottomLabelAlignment(for: attributes)
|
||||
attributes.cellBottomLabelMaxWidth = cellBottomLabelMaxWidth(for: attributes)
|
||||
attributes.cellBottomLabelSize = cellBottomLabelSize(for: attributes)
|
||||
|
||||
// Cell Top Label
|
||||
attributes.cellTopLabelText = cellTopLabelText(for: attributes) // little concerned about storing text here TODO
|
||||
attributes.cellTopLabelAlignment = cellTopLabelAlignment(for: attributes)
|
||||
attributes.cellTopLabelMaxWidth = cellTopLabelMaxWidth(for: attributes)
|
||||
attributes.cellTopLabelSize = cellTopLabelSize(for: attributes)
|
||||
|
||||
// Cell Height
|
||||
attributes.itemHeight = cellHeight(for: attributes)
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
/// The size for the message container.
|
||||
fileprivate func messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
// MARK: - AvatarView Size [ A - C ]
|
||||
|
||||
let maxWidth = messageContainerMaxWidth(for: message, at: indexPath)
|
||||
let messageInsets = messageLabelInsets(for: message, at: indexPath)
|
||||
let messageHorizontalInsets = messageInsets.left + messageInsets.right
|
||||
let messageVerticalInsets = messageInsets.top + messageInsets.bottom
|
||||
// A
|
||||
private func avatarHorizontalAlignment(for attributes: MessageIntermediateLayoutAttributes) -> AvatarHorizontalAlignment {
|
||||
return avatarHorizontalAlignment(for: attributes.message)
|
||||
}
|
||||
// B
|
||||
private func avatarSize(for attributes: MessageIntermediateLayoutAttributes) -> CGSize {
|
||||
return messagesLayoutDelegate.avatarSize(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
}
|
||||
// C
|
||||
private func avatarVerticalAlignment(for attributes: MessageIntermediateLayoutAttributes) -> AvatarAlignment {
|
||||
return messagesLayoutDelegate.avatarAlignment(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
// MARK: - MessageContainerView Size [ D - G ]
|
||||
|
||||
// D
|
||||
private func messageContainerPadding(for attributes: MessageIntermediateLayoutAttributes) -> UIEdgeInsets {
|
||||
return messagesLayoutDelegate.messagePadding(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
// E
|
||||
private func messageLabelInsets(for attributes: MessageIntermediateLayoutAttributes) -> UIEdgeInsets {
|
||||
// Maybe check the message type here since insets only apply to text messages
|
||||
return messagesLayoutDelegate.messageLabelInset(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
// F
|
||||
private func messageContainerMaxWidth(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
|
||||
|
||||
switch attributes.message.data {
|
||||
case .text, .attributedText:
|
||||
return itemWidth - attributes.avatarSize.width - attributes.messageHorizontalPadding - attributes.messageLabelHorizontalInsets
|
||||
default:
|
||||
return itemWidth - attributes.avatarSize.width - attributes.messageHorizontalPadding
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// G
|
||||
private func messageContainerSize(for attributes: MessageIntermediateLayoutAttributes) -> CGSize {
|
||||
|
||||
let message = attributes.message
|
||||
let indexPath = attributes.indexPath
|
||||
let maxWidth = attributes.messageContainerMaxWidth
|
||||
|
||||
var messageContainerSize: CGSize = .zero
|
||||
|
||||
switch message.data {
|
||||
switch attributes.message.data {
|
||||
case .text(let text):
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth, and: messageLabelFont)
|
||||
messageContainerSize.width += messageHorizontalInsets
|
||||
messageContainerSize.height += messageVerticalInsets
|
||||
messageContainerSize.width += attributes.messageLabelHorizontalInsets
|
||||
messageContainerSize.height += attributes.messageLabelVerticalInsets
|
||||
case .attributedText(let text):
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth)
|
||||
messageContainerSize.width += messageHorizontalInsets
|
||||
messageContainerSize.height += messageVerticalInsets
|
||||
messageContainerSize.width += attributes.messageLabelHorizontalInsets
|
||||
messageContainerSize.height += attributes.messageLabelVerticalInsets
|
||||
case .emoji(let text):
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth, and: emojiLabelFont)
|
||||
messageContainerSize.width += attributes.messageLabelHorizontalInsets
|
||||
messageContainerSize.height += attributes.messageLabelVerticalInsets
|
||||
case .photo, .video:
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate as? MediaMessageLayoutDelegate else { return .zero }
|
||||
guard let layoutDelegate = messagesLayoutDelegate as? MediaMessageLayoutDelegate else {
|
||||
fatalError("Layout object attempting to size media message type and MediaMessageLayoutDelegate is not set.")
|
||||
}
|
||||
let width = layoutDelegate.widthForMedia(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
let height = layoutDelegate.heightForMedia(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
messageContainerSize = CGSize(width: width, height: height)
|
||||
case .location:
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate as? LocationMessageLayoutDelegate else { return .zero }
|
||||
guard let layoutDelegate = messagesLayoutDelegate as? LocationMessageLayoutDelegate else {
|
||||
fatalError("Layout object attempting to size location message type and LocationMessageLayoutDelegate is not set.")
|
||||
}
|
||||
let width = layoutDelegate.widthForLocation(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
let height = layoutDelegate.heightForLocation(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
|
||||
messageContainerSize = CGSize(width: width, height: height)
|
||||
case .emoji(let text):
|
||||
messageContainerSize = labelSize(for: text, considering: maxWidth, and: emojiLabelFont)
|
||||
messageContainerSize.width += messageHorizontalInsets
|
||||
messageContainerSize.height += messageVerticalInsets
|
||||
}
|
||||
|
||||
return messageContainerSize
|
||||
}
|
||||
|
||||
/// The origin for the message container.
|
||||
fileprivate func messageContainerOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
|
||||
|
||||
let avatarWidth = attributes.avatarFrame.width
|
||||
|
||||
var origin = CGPoint.zero
|
||||
|
||||
switch attributes.avatarHorizontalAlignment {
|
||||
case .cellLeading:
|
||||
origin.x = avatarWidth + attributes.messagePadding.left
|
||||
origin.y = attributes.cellTopLabelFrame.height + attributes.messagePadding.top
|
||||
case .cellTrailing:
|
||||
origin.x = attributes.frame.width - avatarWidth - attributes.messageContainerFrame.width - attributes.messagePadding.right
|
||||
origin.y = attributes.cellTopLabelFrame.height + attributes.messagePadding.top
|
||||
}
|
||||
|
||||
return origin
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Cell Top Label Calculations
|
||||
// MARK: - Cell Bottom Label Size [ H - K ]
|
||||
|
||||
/// The insets for the cell's top label.
|
||||
private func cellTopLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
let labelAlignment = layoutDelegate.cellTopLabelAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return labelAlignment.insets
|
||||
// H
|
||||
private func cellBottomLabelText(for attributes: MessageIntermediateLayoutAttributes) -> NSAttributedString? {
|
||||
return messagesDataSource.cellBottomLabelAttributedText(for: attributes.message, at: attributes.indexPath)
|
||||
}
|
||||
|
||||
/// The max width available for the cell top label.
|
||||
private func topLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return 0 }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 }
|
||||
|
||||
let labelHorizontal = layoutDelegate.cellTopLabelAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
let avatarVertical = layoutDelegate.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
let avatarHorizontal = avatarHorizontalAlignment(for: message)
|
||||
let topLabelHorizontalInsets = labelHorizontal.insets.left + labelHorizontal.insets.right
|
||||
|
||||
let avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width
|
||||
|
||||
switch (labelHorizontal, avatarHorizontal, avatarVertical) {
|
||||
|
||||
case (.cellLeading, .cellTrailing, .cellTop), (.cellTrailing, .cellLeading, .cellTop):
|
||||
return itemWidth - avatarWidth - topLabelHorizontalInsets
|
||||
|
||||
case (.cellLeading, _, _), (.cellTrailing, _, _):
|
||||
return itemWidth - topLabelHorizontalInsets
|
||||
|
||||
case (.cellCenter, .cellLeading, .cellTop), (.cellCenter, .cellTrailing, .cellTop):
|
||||
return itemWidth - (avatarWidth * 2) - topLabelHorizontalInsets
|
||||
|
||||
case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _):
|
||||
return itemWidth - topLabelHorizontalInsets
|
||||
|
||||
case (.messageTrailing, .cellLeading, .cellTop), (.messageLeading, .cellTrailing, .cellTop):
|
||||
return messageContainerSize(for: message, at: indexPath).width - topLabelHorizontalInsets
|
||||
|
||||
case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _):
|
||||
return itemWidth - avatarWidth - topLabelHorizontalInsets
|
||||
|
||||
case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _):
|
||||
return messageContainerSize(for: message, at: indexPath).width + avatarWidth - topLabelHorizontalInsets
|
||||
}
|
||||
// I
|
||||
private func cellBottomLabelAlignment(for attributes: MessageIntermediateLayoutAttributes) -> LabelAlignment {
|
||||
return messagesLayoutDelegate.cellBottomLabelAlignment(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
/// The size of the cell top label.
|
||||
fileprivate func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
// J
|
||||
private func cellBottomLabelMaxWidth(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
|
||||
guard let topLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return .zero }
|
||||
guard attributes.cellBottomLabelText != nil else { return 0 }
|
||||
|
||||
let maxWidth = topLabelMaxWidth(for: message, at: indexPath)
|
||||
var size = labelSize(for: topLabelText, considering: maxWidth)
|
||||
|
||||
let topLabelInsets = cellTopLabelInsets(for: message, at: indexPath)
|
||||
let topLabelHorizontalInsets = topLabelInsets.left + topLabelInsets.right
|
||||
let topLabelVerticalInsets = topLabelInsets.top + topLabelInsets.bottom
|
||||
|
||||
size.width += topLabelHorizontalInsets
|
||||
size.height += topLabelVerticalInsets
|
||||
|
||||
return size
|
||||
|
||||
}
|
||||
|
||||
/// The origin for the cell top label.
|
||||
fileprivate func cellTopLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
|
||||
let labelAlignment = layoutDelegate.cellTopLabelAlignment(for: message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
let avatarAlignment = avatarHorizontalAlignment(for: message)
|
||||
|
||||
var origin = CGPoint.zero
|
||||
|
||||
switch (labelAlignment, avatarAlignment) {
|
||||
case (.cellLeading, _):
|
||||
origin.x = 0
|
||||
case (.cellCenter, _):
|
||||
origin.x = attributes.frame.width / 2 - (attributes.cellTopLabelFrame.width / 2)
|
||||
case (.cellTrailing, _):
|
||||
origin.x = attributes.frame.width - attributes.cellTopLabelFrame.width
|
||||
case (.messageLeading, .cellLeading):
|
||||
origin.x = attributes.avatarFrame.width + attributes.messagePadding.left
|
||||
case (.messageLeading, .cellTrailing):
|
||||
origin.x = attributes.frame.width - attributes.avatarFrame.width - attributes.messagePadding.right - attributes.messageContainerFrame.width
|
||||
case (.messageTrailing, .cellTrailing):
|
||||
origin.x = attributes.frame.width - attributes.avatarFrame.width - attributes.messagePadding.right - attributes.cellTopLabelFrame.width
|
||||
case (.messageTrailing, .cellLeading):
|
||||
origin.x = attributes.frame.width - attributes.messagePadding.right - attributes.cellTopLabelFrame.width
|
||||
}
|
||||
|
||||
return origin
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Cell Bottom Label Calculations
|
||||
|
||||
/// The insets for the cell's top label.
|
||||
private func cellBottomLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets {
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
let labelAlignment = layoutDelegate.cellBottomLabelAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
return labelAlignment.insets
|
||||
}
|
||||
|
||||
/// The max with available for the cell bottom label.
|
||||
private func bottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return 0 }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 }
|
||||
|
||||
let labelHorizontal = layoutDelegate.cellBottomLabelAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
let avatarVertical = layoutDelegate.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
let avatarHorizontal = avatarHorizontalAlignment(for: message)
|
||||
let bottomLabelHorizontalInsets = labelHorizontal.insets.left + labelHorizontal.insets.right
|
||||
|
||||
let avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width
|
||||
let labelHorizontal = attributes.cellBottomLabelAlignment
|
||||
let avatarHorizontal = attributes.avatarHorizontal
|
||||
let avatarVertical = attributes.avatarVertical
|
||||
let avatarWidth = attributes.avatarSize.width
|
||||
|
||||
switch (labelHorizontal, avatarHorizontal, avatarVertical) {
|
||||
|
||||
case (.cellLeading, .cellTrailing, .cellBottom), (.cellTrailing, .cellLeading, .cellBottom):
|
||||
return itemWidth - avatarWidth - bottomLabelHorizontalInsets
|
||||
return itemWidth - avatarWidth - attributes.cellBottomLabelHorizontalInsets
|
||||
|
||||
case (.cellLeading, _, _), (.cellTrailing, _, _):
|
||||
return itemWidth - bottomLabelHorizontalInsets
|
||||
return itemWidth - attributes.cellBottomLabelHorizontalInsets
|
||||
|
||||
case (.cellCenter, .cellLeading, .cellBottom), (.cellCenter, .cellTrailing, .cellBottom):
|
||||
return itemWidth - (avatarWidth * 2) - bottomLabelHorizontalInsets
|
||||
return itemWidth - (avatarWidth * 2) - attributes.cellBottomLabelHorizontalInsets
|
||||
|
||||
case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _):
|
||||
return itemWidth - bottomLabelHorizontalInsets
|
||||
return itemWidth - attributes.cellBottomLabelHorizontalInsets
|
||||
|
||||
case (.messageTrailing, .cellLeading, .cellBottom), (.messageLeading, .cellTrailing, .cellBottom):
|
||||
return messageContainerSize(for: message, at: indexPath).width - bottomLabelHorizontalInsets
|
||||
return attributes.messageContainerSize.width - attributes.cellBottomLabelHorizontalInsets
|
||||
|
||||
case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _):
|
||||
return itemWidth - avatarWidth - bottomLabelHorizontalInsets
|
||||
return itemWidth - avatarWidth - attributes.cellBottomLabelHorizontalInsets
|
||||
|
||||
case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _):
|
||||
return messageContainerSize(for: message, at: indexPath).width + avatarWidth - bottomLabelHorizontalInsets
|
||||
|
||||
return attributes.messageContainerSize.width + avatarWidth - attributes.cellBottomLabelHorizontalInsets
|
||||
}
|
||||
}
|
||||
|
||||
/// The size for the cell bottom label.
|
||||
fileprivate func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
|
||||
guard let bottomLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return .zero }
|
||||
|
||||
let maxWidth = bottomLabelMaxWidth(for: message, at: indexPath)
|
||||
var size = labelSize(for: bottomLabelText, considering: maxWidth)
|
||||
|
||||
let bottomLabelInsets = cellBottomLabelInsets(for: message, at: indexPath)
|
||||
let bottomLabelHorizontalInsets = bottomLabelInsets.left + bottomLabelInsets.right
|
||||
let bottomLabelVerticalInsets = bottomLabelInsets.top + bottomLabelInsets.bottom
|
||||
|
||||
size.width += bottomLabelHorizontalInsets
|
||||
size.height += bottomLabelVerticalInsets
|
||||
|
||||
return size
|
||||
|
||||
}
|
||||
|
||||
/// The origin for the cell bottom label.
|
||||
fileprivate func cellBottomLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
|
||||
// K
|
||||
private func cellBottomLabelSize(for attributes: MessageIntermediateLayoutAttributes) -> CGSize {
|
||||
guard let bottomLabelText = attributes.cellBottomLabelText else { return .zero }
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
|
||||
var bottomLabelSize = labelSize(for: bottomLabelText, considering: attributes.cellBottomLabelMaxWidth)
|
||||
bottomLabelSize.width += attributes.cellBottomLabelHorizontalInsets
|
||||
bottomLabelSize.height += attributes.cellBottomLabelVerticalInsets
|
||||
|
||||
let labelAlignment = layoutDelegate.cellBottomLabelAlignment(for: message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
let avatarAlignment = avatarHorizontalAlignment(for: message)
|
||||
return bottomLabelSize
|
||||
|
||||
var origin = CGPoint(x: 0, y: attributes.frame.height - attributes.cellBottomLabelFrame.height)
|
||||
}
|
||||
|
||||
switch (labelAlignment, avatarAlignment) {
|
||||
case (.cellLeading, _):
|
||||
// MARK: - Cell Top Label Sizing [ L - O ]
|
||||
|
||||
// L
|
||||
private func cellTopLabelText(for attributes: MessageIntermediateLayoutAttributes) -> NSAttributedString? {
|
||||
return messagesDataSource.cellTopLabelAttributedText(for: attributes.message, at: attributes.indexPath)
|
||||
}
|
||||
|
||||
// M
|
||||
private func cellTopLabelAlignment(for attributes: MessageIntermediateLayoutAttributes) -> LabelAlignment {
|
||||
return messagesLayoutDelegate.cellTopLabelAlignment(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
|
||||
}
|
||||
|
||||
// N
|
||||
private func cellTopLabelMaxWidth(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
|
||||
|
||||
guard attributes.cellTopLabelText != nil else { return 0 }
|
||||
|
||||
let labelHorizontal = attributes.cellTopLabelAlignment
|
||||
let avatarHorizontal = attributes.avatarHorizontal
|
||||
let avatarVertical = attributes.avatarVertical
|
||||
let avatarWidth = attributes.avatarSize.width
|
||||
|
||||
switch (labelHorizontal, avatarHorizontal, avatarVertical) {
|
||||
|
||||
case (.cellLeading, .cellTrailing, .cellTop), (.cellTrailing, .cellLeading, .cellTop):
|
||||
return itemWidth - avatarWidth - attributes.cellTopLabelHorizontalInsets
|
||||
|
||||
case (.cellLeading, _, _), (.cellTrailing, _, _):
|
||||
return itemWidth - attributes.cellTopLabelHorizontalInsets
|
||||
|
||||
case (.cellCenter, .cellLeading, .cellTop), (.cellCenter, .cellTrailing, .cellTop):
|
||||
return itemWidth - (avatarWidth * 2) - attributes.cellTopLabelHorizontalInsets
|
||||
|
||||
case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _):
|
||||
return itemWidth - attributes.cellTopLabelHorizontalInsets
|
||||
|
||||
case (.messageTrailing, .cellLeading, .cellTop), (.messageLeading, .cellTrailing, .cellTop):
|
||||
return attributes.messageContainerSize.width - attributes.cellTopLabelHorizontalInsets
|
||||
|
||||
case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _):
|
||||
return itemWidth - avatarWidth - attributes.cellTopLabelHorizontalInsets
|
||||
|
||||
case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _):
|
||||
return attributes.messageContainerSize.width + avatarWidth - attributes.cellTopLabelHorizontalInsets
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// O
|
||||
private func cellTopLabelSize(for attributes: MessageIntermediateLayoutAttributes) -> CGSize {
|
||||
guard let topLabelText = attributes.cellTopLabelText else { return .zero }
|
||||
|
||||
var topLabelSize = labelSize(for: topLabelText, considering: attributes.cellTopLabelMaxWidth)
|
||||
topLabelSize.width += attributes.cellTopLabelHorizontalInsets
|
||||
topLabelSize.height += attributes.cellTopLabelVerticalInsets
|
||||
|
||||
return topLabelSize
|
||||
}
|
||||
|
||||
// MARK: - Cell Height [ P ]
|
||||
|
||||
// P
|
||||
private func cellHeight(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
|
||||
|
||||
var cellHeight: CGFloat = 0
|
||||
|
||||
switch attributes.avatarVertical {
|
||||
case .cellTop:
|
||||
cellHeight += max(attributes.avatarSize.height, attributes.cellTopLabelSize.height)
|
||||
cellHeight += attributes.cellBottomLabelSize.height
|
||||
cellHeight += attributes.messageContainerSize.height
|
||||
cellHeight += attributes.messageVerticalPadding
|
||||
case .cellBottom:
|
||||
cellHeight += max(attributes.avatarSize.height, attributes.cellBottomLabelSize.height)
|
||||
cellHeight += attributes.cellTopLabelSize.height
|
||||
cellHeight += attributes.messageContainerSize.height
|
||||
cellHeight += attributes.messageVerticalPadding
|
||||
case .messageTop, .messageCenter, .messageBottom:
|
||||
cellHeight += max(attributes.avatarSize.height, attributes.messageContainerSize.height)
|
||||
cellHeight += attributes.messageVerticalPadding
|
||||
cellHeight += attributes.cellTopLabelSize.height
|
||||
cellHeight += attributes.cellBottomLabelSize.height
|
||||
}
|
||||
|
||||
return cellHeight
|
||||
}
|
||||
|
||||
// MARK: - Origins [ Q - T ]
|
||||
|
||||
// Q
|
||||
private func avatarOrigin(for attributes: MessageIntermediateLayoutAttributes, and contentFrame: CGRect) -> CGPoint {
|
||||
|
||||
guard attributes.avatarSize != .zero else { return .zero }
|
||||
|
||||
var origin = CGPoint.zero
|
||||
|
||||
switch attributes.avatarHorizontal {
|
||||
case .cellLeading:
|
||||
origin.x = 0
|
||||
case (.cellCenter, _):
|
||||
origin.x = attributes.frame.width / 2 - (attributes.cellBottomLabelFrame.width / 2)
|
||||
case (.cellTrailing, _):
|
||||
origin.x = attributes.frame.width - attributes.cellBottomLabelFrame.width
|
||||
case (.messageLeading, .cellLeading):
|
||||
origin.x = attributes.avatarFrame.width + attributes.messagePadding.left
|
||||
case (.messageLeading, .cellTrailing):
|
||||
origin.x = attributes.frame.width - attributes.avatarFrame.width - attributes.messagePadding.right - attributes.messageContainerFrame.width
|
||||
case (.messageTrailing, .cellTrailing):
|
||||
origin.x = attributes.frame.width - attributes.avatarFrame.width - attributes.messagePadding.right - attributes.cellBottomLabelFrame.width
|
||||
case (.messageTrailing, .cellLeading):
|
||||
origin.x = attributes.avatarFrame.width + attributes.messagePadding.left + attributes.messageContainerFrame.width - attributes.cellBottomLabelFrame.width
|
||||
case .cellTrailing:
|
||||
origin.x = contentFrame.width - attributes.avatarSize.width
|
||||
}
|
||||
|
||||
switch attributes.avatarVertical {
|
||||
case .cellTop:
|
||||
origin.y = 0
|
||||
case .cellBottom:
|
||||
origin.y = contentFrame.height - attributes.avatarSize.height
|
||||
case .messageTop:
|
||||
origin.y = attributes.cellTopLabelSize.height + attributes.messageContainerPadding.top
|
||||
case .messageBottom:
|
||||
origin.y = contentFrame.height - attributes.avatarSize.height - attributes.cellBottomLabelSize.height - attributes.messageContainerPadding.bottom
|
||||
case .messageCenter:
|
||||
let messageMidY = attributes.messageContainerSize.height / 2
|
||||
let avatarMidY = attributes.avatarSize.height / 2
|
||||
origin.y = attributes.cellTopLabelSize.height + attributes.messageContainerPadding.top + messageMidY - avatarMidY
|
||||
}
|
||||
|
||||
return origin
|
||||
}
|
||||
|
||||
// R
|
||||
private func messageContainerOrigin(for attributes: MessageIntermediateLayoutAttributes, and contentFrame: CGRect) -> CGPoint {
|
||||
|
||||
guard attributes.messageContainerSize != .zero else { return .zero }
|
||||
|
||||
var origin = CGPoint.zero
|
||||
|
||||
switch attributes.avatarHorizontal {
|
||||
case .cellLeading:
|
||||
origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left
|
||||
origin.y = attributes.cellTopLabelSize.height + attributes.messageContainerPadding.top
|
||||
case .cellTrailing:
|
||||
origin.x = contentFrame.width - attributes.avatarSize.width - attributes.messageContainerSize.width - attributes.messageContainerPadding.right
|
||||
origin.y = attributes.cellTopLabelSize.height + attributes.messageContainerPadding.top
|
||||
}
|
||||
|
||||
return origin
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Cell Size Calculations
|
||||
// S
|
||||
private func cellBottomLabelOrigin(for attributes: MessageIntermediateLayoutAttributes, and contentFrame: CGRect) -> CGPoint {
|
||||
|
||||
/// The minimum height for the cell.
|
||||
private func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
guard attributes.cellBottomLabelSize != .zero else { return .zero }
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return 0 }
|
||||
guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 }
|
||||
var origin = CGPoint(x: 0, y: contentFrame.height - attributes.cellBottomLabelSize.height)
|
||||
|
||||
let avatarAlignment = layoutDelegate.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView)
|
||||
|
||||
let topLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
|
||||
let avatarHeight = avatarSize(for: message, at: indexPath).height
|
||||
let bottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height
|
||||
let messagePadding = messageContainerPadding(for: message, at: indexPath)
|
||||
let messagePaddingVertical = messagePadding.top + messagePadding.bottom
|
||||
|
||||
switch avatarAlignment {
|
||||
case .cellTop:
|
||||
return max(avatarHeight, topLabelHeight) + bottomLabelHeight + messagePaddingVertical
|
||||
case .cellBottom:
|
||||
return max(avatarHeight, bottomLabelHeight) + topLabelHeight + messagePaddingVertical
|
||||
case .messageTop, .messageCenter, .messageBottom:
|
||||
return topLabelHeight + avatarHeight + bottomLabelHeight + messagePaddingVertical
|
||||
switch (attributes.cellBottomLabelAlignment, attributes.avatarHorizontal) {
|
||||
case (.cellLeading, _):
|
||||
origin.x = 0
|
||||
case (.cellCenter, _):
|
||||
origin.x = contentFrame.width / 2 - (attributes.cellBottomLabelSize.width / 2)
|
||||
case (.cellTrailing, _):
|
||||
origin.x = contentFrame.width - attributes.cellBottomLabelSize.width
|
||||
case (.messageLeading, .cellLeading):
|
||||
origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left
|
||||
case (.messageLeading, .cellTrailing):
|
||||
origin.x = contentFrame.width - attributes.avatarSize.width - attributes.messageContainerPadding.right - attributes.cellBottomLabelSize.width
|
||||
case (.messageTrailing, .cellTrailing):
|
||||
origin.x = contentFrame.width - attributes.avatarSize.width - attributes.messageContainerPadding.right - attributes.cellBottomLabelSize.width
|
||||
case (.messageTrailing, .cellLeading):
|
||||
origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left + attributes.messageContainerSize.width - attributes.cellBottomLabelSize.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// The calculated height of the cell not considering the minimum height.
|
||||
private func estimatedCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
let messageContainerHeight = messageContainerSize(for: message, at: indexPath).height
|
||||
let cellTopLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
|
||||
let cellBottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height
|
||||
let messagePadding = messageContainerPadding(for: message, at: indexPath)
|
||||
let messagePaddingVertical = messagePadding.top + messagePadding.bottom
|
||||
|
||||
return messageContainerHeight + cellBottomLabelHeight + cellTopLabelHeight + messagePaddingVertical
|
||||
return origin
|
||||
|
||||
}
|
||||
|
||||
/// The size for the cell considering all cell contents.
|
||||
open func sizeForItem(at indexPath: IndexPath) -> CGSize {
|
||||
// T
|
||||
fileprivate func cellTopLabelOrigin(for attributes: MessageIntermediateLayoutAttributes, and contentFrame: CGRect) -> CGPoint {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return .zero }
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
|
||||
guard attributes.cellTopLabelSize != .zero else { return .zero }
|
||||
|
||||
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
var origin = CGPoint.zero
|
||||
|
||||
let minimumHeight = minimumCellHeight(for: message, at: indexPath)
|
||||
let estimatedHeight = estimatedCellHeight(for: message, at: indexPath)
|
||||
switch (attributes.cellTopLabelAlignment, attributes.avatarHorizontal) {
|
||||
case (.cellLeading, _):
|
||||
origin.x = 0
|
||||
case (.cellCenter, _):
|
||||
origin.x = contentFrame.width / 2 - (attributes.cellTopLabelSize.width / 2)
|
||||
case (.cellTrailing, _):
|
||||
origin.x = contentFrame.width - attributes.cellTopLabelSize.width
|
||||
case (.messageLeading, .cellLeading):
|
||||
origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left
|
||||
case (.messageLeading, .cellTrailing):
|
||||
origin.x = contentFrame.width - attributes.avatarSize.width - attributes.messageContainerPadding.right - attributes.messageContainerSize.width
|
||||
case (.messageTrailing, .cellTrailing):
|
||||
origin.x = contentFrame.width - attributes.avatarSize.width - attributes.messageContainerPadding.right - attributes.cellTopLabelSize.width
|
||||
case (.messageTrailing, .cellLeading):
|
||||
origin.x = contentFrame.width - attributes.messageContainerPadding.right - attributes.cellTopLabelSize.width
|
||||
}
|
||||
|
||||
let itemHeight = estimatedHeight > minimumHeight ? estimatedHeight : minimumHeight
|
||||
|
||||
return CGSize(width: itemWidth, height: itemHeight)
|
||||
return origin
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user