diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 8bd75481..e260c1e4 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -22,4 +22,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 90bd12ca685503630d7fe716609f4974660ed5ee -COCOAPODS: 1.9.1 +COCOAPODS: 1.10.1 diff --git a/MessageKit.podspec b/MessageKit.podspec index 06b1f220..665bce06 100644 --- a/MessageKit.podspec +++ b/MessageKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'MessageKit' - s.version = '3.1.0' + s.version = '3.1.1' s.license = { :type => "MIT", :file => "LICENSE.md" } s.summary = 'An elegant messages UI library for iOS.' diff --git a/Sources/Controllers/MessagesViewController.swift b/Sources/Controllers/MessagesViewController.swift index e74b0183..5c3d24c4 100644 --- a/Sources/Controllers/MessagesViewController.swift +++ b/Sources/Controllers/MessagesViewController.swift @@ -284,25 +284,45 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { switch message.kind { case .text, .attributedText, .emoji: - let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath) - cell.configure(with: message, at: indexPath, and: messagesCollectionView) - return cell + if let cell = messagesDataSource.textCell(for: message, at: indexPath, in: messagesCollectionView) { + return cell + } else { + let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath) + cell.configure(with: message, at: indexPath, and: messagesCollectionView) + return cell + } case .photo, .video: - let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath) - cell.configure(with: message, at: indexPath, and: messagesCollectionView) - return cell + if let cell = messagesDataSource.photoCell(for: message, at: indexPath, in: messagesCollectionView) { + return cell + } else { + let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath) + cell.configure(with: message, at: indexPath, and: messagesCollectionView) + return cell + } case .location: - let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath) - cell.configure(with: message, at: indexPath, and: messagesCollectionView) - return cell + if let cell = messagesDataSource.locationCell(for: message, at: indexPath, in: messagesCollectionView) { + return cell + } else { + let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath) + cell.configure(with: message, at: indexPath, and: messagesCollectionView) + return cell + } case .audio: - let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath) - cell.configure(with: message, at: indexPath, and: messagesCollectionView) - return cell + if let cell = messagesDataSource.audioCell(for: message, at: indexPath, in: messagesCollectionView) { + return cell + } else { + let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath) + cell.configure(with: message, at: indexPath, and: messagesCollectionView) + return cell + } case .contact: - let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath) - cell.configure(with: message, at: indexPath, and: messagesCollectionView) - return cell + if let cell = messagesDataSource.contactCell(for: message, at: indexPath, in: messagesCollectionView) { + return cell + } else { + let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath) + cell.configure(with: message, at: indexPath, and: messagesCollectionView) + return cell + } case .custom: return messagesDataSource.customCell(for: message, at: indexPath, in: messagesCollectionView) } diff --git a/Sources/Layout/MessagesCollectionViewFlowLayout.swift b/Sources/Layout/MessagesCollectionViewFlowLayout.swift index 95ac736d..5b73d545 100644 --- a/Sources/Layout/MessagesCollectionViewFlowLayout.swift +++ b/Sources/Layout/MessagesCollectionViewFlowLayout.swift @@ -182,21 +182,21 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView) switch message.kind { case .text: - return textMessageSizeCalculator + return messagesLayoutDelegate.textCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? textMessageSizeCalculator case .attributedText: - return attributedTextMessageSizeCalculator + return messagesLayoutDelegate.attributedTextCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? attributedTextMessageSizeCalculator case .emoji: - return emojiMessageSizeCalculator + return messagesLayoutDelegate.emojiCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? emojiMessageSizeCalculator case .photo: - return photoMessageSizeCalculator + return messagesLayoutDelegate.photoCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? photoMessageSizeCalculator case .video: - return videoMessageSizeCalculator + return messagesLayoutDelegate.videoCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? videoMessageSizeCalculator case .location: - return locationMessageSizeCalculator + return messagesLayoutDelegate.locationCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? locationMessageSizeCalculator case .audio: - return audioMessageSizeCalculator + return messagesLayoutDelegate.audioCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? audioMessageSizeCalculator case .contact: - return contactMessageSizeCalculator + return messagesLayoutDelegate.contactCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) ?? contactMessageSizeCalculator case .custom: return messagesLayoutDelegate.customCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView) } diff --git a/Sources/Protocols/MessagesDataSource.swift b/Sources/Protocols/MessagesDataSource.swift index 9db208f7..66391e92 100644 --- a/Sources/Protocols/MessagesDataSource.swift +++ b/Sources/Protocols/MessagesDataSource.swift @@ -103,6 +103,61 @@ public protocol MessagesDataSource: AnyObject { /// The default value returned by this method is `nil`. func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? + /// Text collectionView cell for message with `text`, `attributedText`, `emoji` message types. + /// + /// - Parameters: + /// - message: The `text`, `attributedText`, `emoji` message type + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// This method will return nil by default. You must override this method only if you want your own cell. + func textCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? + + /// Photo or Video collectionView cell for message with `photo`, `video` message types. + /// + /// - Parameters: + /// - message: The `photo`, `video` message type + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// This method will return nil by default. You must override this method only if you want your own cell. + func photoCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? + + /// Location collectionView cell for message with `location` message type. + /// + /// - Parameters: + /// - message: The `location` message type + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// This method will return nil by default. You must override this method only if you want your own cell. + func locationCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? + + /// Audio collectionView cell for message with `audio` message type. + /// + /// - Parameters: + /// - message: The `audio` message type + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// This method will return nil by default. You must override this method only if you want your own cell. + func audioCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? + + /// Contact collectionView cell for message with `contact` message type. + /// + /// - Parameters: + /// - message: The `contact` message type + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// This method will return nil by default. You must override this method only if you want your own cell. + func contactCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? + /// Custom collectionView cell for message with `custom` message type. /// /// - Parameters: @@ -149,6 +204,26 @@ public extension MessagesDataSource { return nil } + func textCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + return nil + } + + func photoCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + return nil + } + + func locationCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + return nil + } + + func audioCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + return nil + } + + func contactCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + return nil + } + func customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell { fatalError(MessageKitError.customDataUnresolvedCell) } diff --git a/Sources/Protocols/MessagesLayoutDelegate.swift b/Sources/Protocols/MessagesLayoutDelegate.swift index 3bdcb363..9986c409 100644 --- a/Sources/Protocols/MessagesLayoutDelegate.swift +++ b/Sources/Protocols/MessagesLayoutDelegate.swift @@ -111,6 +111,94 @@ 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 + /// Text cell size calculator for messages with MessageType.text. + /// + /// - Parameters: + /// - message: The text message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.text. + func textCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Attributed text cell size calculator for messages with MessageType.attributedText. + /// + /// - Parameters: + /// - message: The attributedText message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.attributedText. + func attributedTextCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Emoji cell size calculator for messages with MessageType.emoji. + /// + /// - Parameters: + /// - message: The emoji message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.emoji. + func emojiCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Photo cell size calculator for messages with MessageType.photo. + /// + /// - Parameters: + /// - message: The photo message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.text. + func photoCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Video cell size calculator for messages with MessageType.video. + /// + /// - Parameters: + /// - message: The video message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.video. + func videoCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Location cell size calculator for messages with MessageType.location. + /// + /// - Parameters: + /// - message: The location message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.location. + func locationCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Audio cell size calculator for messages with MessageType.audio. + /// + /// - Parameters: + /// - message: The audio message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.audio. + func audioCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + + /// Constact cell size calculator for messages with MessageType.contact. + /// + /// - Parameters: + /// - message: The contact message + /// - indexPath: The `IndexPath` of the cell. + /// - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed. + /// + /// - Note: + /// The default implementation will return nil. You must override this method if you are using your own cell for messages with MessageType.contact. + func contactCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? + /// Custom cell size calculator for messages with MessageType.custom. /// /// - Parameters: @@ -157,6 +245,38 @@ public extension MessagesLayoutDelegate { return 0 } + func textCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func attributedTextCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func emojiCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func photoCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func videoCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func locationCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func audioCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + + func contactCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } + func customCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator { fatalError("Must return a CellSizeCalculator for MessageKind.custom(Any?)") }