From 9c23282d0713d71ef4e2cea55c8178ba7db51b13 Mon Sep 17 00:00:00 2001 From: Shawn Gong Date: Sat, 2 Jun 2018 02:10:51 -0700 Subject: [PATCH] Add customizable accessoryView besides messageContainer --- .../Controllers/MessagesViewController.swift | 3 ++ Sources/Layout/MessageSizeCalculator.swift | 21 ++++++++++++- ...ssagesCollectionViewLayoutAttributes.swift | 7 +++++ Sources/Protocols/MessagesDataSource.swift | 9 ++++++ .../Protocols/MessagesLayoutDelegate.swift | 27 +++++++++++++++++ Sources/Views/Cells/MessageContentCell.swift | 30 ++++++++++++++++++- 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/Sources/Controllers/MessagesViewController.swift b/Sources/Controllers/MessagesViewController.swift index 859ba37d..ebf36b76 100644 --- a/Sources/Controllers/MessagesViewController.swift +++ b/Sources/Controllers/MessagesViewController.swift @@ -180,14 +180,17 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { case .text, .attributedText, .emoji: let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath) cell.configure(with: message, at: indexPath, and: messagesCollectionView) + messagesDataSource.configCell(cell, for: message, at: indexPath) return cell case .photo, .video: let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath) cell.configure(with: message, at: indexPath, and: messagesCollectionView) + messagesDataSource.configCell(cell, for: message, at: indexPath) return cell case .location: let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath) cell.configure(with: message, at: indexPath, and: messagesCollectionView) + messagesDataSource.configCell(cell, for: message, at: indexPath) return cell case .custom: fatalError(MessageKitError.customDataUnresolvedCell) diff --git a/Sources/Layout/MessageSizeCalculator.swift b/Sources/Layout/MessageSizeCalculator.swift index 7e563629..2cc3caa5 100644 --- a/Sources/Layout/MessageSizeCalculator.swift +++ b/Sources/Layout/MessageSizeCalculator.swift @@ -68,6 +68,9 @@ open class MessageSizeCalculator: CellSizeCalculator { attributes.messageBottomLabelAlignment = messageBottomLabelAlignment(for: message) attributes.messageBottomLabelSize = messageBottomLabelSize(for: message, at: indexPath) + + attributes.accessoryViewSize = accessoryViewSize(for: message) + attributes.accessoryViewPadding = accessoryViewPadding(for: message) } open override func sizeForItem(at indexPath: IndexPath) -> CGSize { @@ -185,6 +188,20 @@ open class MessageSizeCalculator: CellSizeCalculator { return isFromCurrentSender ? outgoingMessageBottomLabelAlignment : incomingMessageBottomLabelAlignment } + // MARK: - Accessory View + + public func accessoryViewSize(for message: MessageType) -> CGSize { + let layoutDelegate = messagesLayout.messagesLayoutDelegate + let collectionView = messagesLayout.messagesCollectionView + return layoutDelegate.accessoryViewSize(for: message, in: collectionView) + } + + public func accessoryViewPadding(for message: MessageType) -> UIEdgeInsets { + let layoutDelegate = messagesLayout.messagesLayoutDelegate + let collectionView = messagesLayout.messagesCollectionView + return layoutDelegate.accessoryViewPadding(for: message, in: collectionView) + } + // MARK: - MessageContainer public func messageContainerPadding(for message: MessageType) -> UIEdgeInsets { @@ -201,7 +218,9 @@ open class MessageSizeCalculator: CellSizeCalculator { open func messageContainerMaxWidth(for message: MessageType) -> CGFloat { let avatarWidth = avatarSize(for: message).width let messagePadding = messageContainerPadding(for: message) - return messagesLayout.itemWidth - avatarWidth - messagePadding.horizontal + let accessoryWidth = accessoryViewSize(for: message).width + let accessoryPadding = accessoryViewPadding(for: message) + return messagesLayout.itemWidth - avatarWidth - messagePadding.horizontal - accessoryWidth - accessoryPadding.horizontal } // MARK: - Helpers diff --git a/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift b/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift index 678645a2..9dcde3cf 100644 --- a/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift +++ b/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift @@ -46,6 +46,9 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu public var messageBottomLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero) public var messageBottomLabelSize: CGSize = .zero + public var accessoryViewSize: CGSize = .zero + public var accessoryViewPadding: UIEdgeInsets = .zero + // MARK: - Methods open override func copy(with zone: NSZone? = nil) -> Any { @@ -63,6 +66,8 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu copy.messageTopLabelSize = messageTopLabelSize copy.messageBottomLabelAlignment = messageBottomLabelAlignment copy.messageBottomLabelSize = messageBottomLabelSize + copy.accessoryViewSize = accessoryViewSize + copy.accessoryViewPadding = accessoryViewPadding return copy // swiftlint:enable force_cast } @@ -82,6 +87,8 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu && attributes.messageTopLabelSize == messageTopLabelSize && attributes.messageBottomLabelAlignment == messageBottomLabelAlignment && attributes.messageBottomLabelSize == messageBottomLabelSize + && attributes.accessoryViewSize == accessoryViewSize + && attributes.accessoryViewPadding == accessoryViewPadding } else { return false } diff --git a/Sources/Protocols/MessagesDataSource.swift b/Sources/Protocols/MessagesDataSource.swift index e9ee46f7..1b22aea0 100644 --- a/Sources/Protocols/MessagesDataSource.swift +++ b/Sources/Protocols/MessagesDataSource.swift @@ -93,6 +93,14 @@ public protocol MessagesDataSource: AnyObject { /// The default value returned by this method is `nil`. func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? + /// Provide a chance to config a cell before it's presented. e.g. to customize the accessoryView. + /// + /// - Parameters: + /// - cell: The `MessageContentCell` that can be configured. + /// - message: The `MessageType` that will be displayed by this cell. + /// - indexPath: The `IndexPath` of the cell. + /// + func configCell(_ cell: MessageContentCell, for message: MessageType, at indexPath: IndexPath) } public extension MessagesDataSource { @@ -117,4 +125,5 @@ public extension MessagesDataSource { return nil } + func configCell(_ cell: MessageCollectionViewCell, for message: MessageType, at indexPath: IndexPath) {} } diff --git a/Sources/Protocols/MessagesLayoutDelegate.swift b/Sources/Protocols/MessagesLayoutDelegate.swift index 6f825fbf..1161cb42 100644 --- a/Sources/Protocols/MessagesLayoutDelegate.swift +++ b/Sources/Protocols/MessagesLayoutDelegate.swift @@ -81,6 +81,25 @@ public protocol MessagesLayoutDelegate: AnyObject { /// The default value returned by this method is zero. func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat + /// Specifies the size for the `MessageContentCell`'s accessory view. + /// + /// - Parameters: + /// - message: The `MessageType` that will be displayed for this cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default value returned by this method is zero. + func accessoryViewSize(for message: MessageType, in messagesCollectionView: MessagesCollectionView) -> CGSize + + /// Specifies the padding for the `MessageContentCell`'s accessory view. + /// + /// - Parameters: + /// - message: The `MessageType` that will be displayed for this cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default value returned by this method is zero. + func accessoryViewPadding(for message: MessageType, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets } public extension MessagesLayoutDelegate { @@ -104,4 +123,12 @@ public extension MessagesLayoutDelegate { func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 0 } + + func accessoryViewSize(for message: MessageType, in messagesCollectionView: MessagesCollectionView) -> CGSize { + return .zero + } + + func accessoryViewPadding(for message: MessageType, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets { + return .zero + } } diff --git a/Sources/Views/Cells/MessageContentCell.swift b/Sources/Views/Cells/MessageContentCell.swift index 81fc338e..6010b604 100644 --- a/Sources/Views/Cells/MessageContentCell.swift +++ b/Sources/Views/Cells/MessageContentCell.swift @@ -60,6 +60,9 @@ open class MessageContentCell: MessageCollectionViewCell { return label }() + // Should only add customized subviews - don't change accessoryView itself. + open var accessoryView: UIView = .init() + /// The `MessageCellDelegate` for the cell. open weak var delegate: MessageCellDelegate? @@ -74,6 +77,7 @@ open class MessageContentCell: MessageCollectionViewCell { } open func setupSubviews() { + contentView.addSubview(accessoryView) contentView.addSubview(cellTopLabel) contentView.addSubview(messageTopLabel) contentView.addSubview(messageBottomLabel) @@ -86,6 +90,9 @@ open class MessageContentCell: MessageCollectionViewCell { cellTopLabel.text = nil messageTopLabel.text = nil messageBottomLabel.text = nil + for view in accessoryView.subviews { + view.removeFromSuperview() + } } // MARK: - Configuration @@ -99,6 +106,7 @@ open class MessageContentCell: MessageCollectionViewCell { layoutCellTopLabel(with: attributes) layoutMessageTopLabel(with: attributes) layoutAvatarView(with: attributes) + layoutAccessoryView(with: attributes) } /// Used to configure the cell. @@ -270,5 +278,25 @@ open class MessageContentCell: MessageCollectionViewCell { messageBottomLabel.frame = CGRect(origin: origin, size: attributes.messageBottomLabelSize) } - + + /// Positions the cell's accessory view. + /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell. + open func layoutAccessoryView(with attributes: MessagesCollectionViewLayoutAttributes) { + guard attributes.accessoryViewSize != .zero else { return } + + let y = (bounds.height - attributes.accessoryViewSize.height) / 2 + var origin = CGPoint(x: 0, y: y) + + // Accessory view is always on the opposite side of avatar + switch attributes.avatarPosition.horizontal { + case .cellLeading: + origin.x = messageContainerView.frame.maxX + attributes.accessoryViewPadding.left + case .cellTrailing: + origin.x = messageContainerView.frame.minX - attributes.accessoryViewPadding.right - attributes.accessoryViewSize.width + case .natural: + fatalError(MessageKitError.avatarPositionUnresolved) + } + + accessoryView.frame = CGRect(origin: origin, size: attributes.accessoryViewSize) + } }