diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 357aaf55..67d8f2ce 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -44,6 +44,11 @@ 882B5E901CF7D56000B6E160 /* ChatExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882B5E8E1CF7D56000B6E160 /* ChatExampleUITests.swift */; }; 882B5E951CF7D56E00B6E160 /* ChatExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882B5E931CF7D56E00B6E160 /* ChatExampleTests.swift */; }; 9961327FB4F9B96CFD84A126 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 9B49D528263C31FC008804B5 /* CustomLayoutExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B49D527263C31FC008804B5 /* CustomLayoutExampleViewController.swift */; }; + 9B49D52D263C3E1F008804B5 /* CustomTextLayoutSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B49D52C263C3E1F008804B5 /* CustomTextLayoutSizeCalculator.swift */; }; + 9B49D53A263D9606008804B5 /* CustomLayoutSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B49D539263D9606008804B5 /* CustomLayoutSizeCalculator.swift */; }; + 9B49D542263DA6F9008804B5 /* CustomMessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B49D541263DA6F9008804B5 /* CustomMessageContentCell.swift */; }; + 9B49D547263DAA29008804B5 /* CustomTextMessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B49D546263DAA29008804B5 /* CustomTextMessageContentCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -105,6 +110,11 @@ 882B5E8F1CF7D56000B6E160 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 882B5E931CF7D56E00B6E160 /* ChatExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatExampleTests.swift; sourceTree = ""; }; 882B5E941CF7D56E00B6E160 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9B49D527263C31FC008804B5 /* CustomLayoutExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLayoutExampleViewController.swift; sourceTree = ""; }; + 9B49D52C263C3E1F008804B5 /* CustomTextLayoutSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextLayoutSizeCalculator.swift; sourceTree = ""; }; + 9B49D539263D9606008804B5 /* CustomLayoutSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLayoutSizeCalculator.swift; sourceTree = ""; }; + 9B49D541263DA6F9008804B5 /* CustomMessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMessageContentCell.swift; sourceTree = ""; }; + 9B49D546263DAA29008804B5 /* CustomTextMessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextMessageContentCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -152,6 +162,8 @@ 385C2920211FF32E0010B4BA /* CustomCell.swift */, 385C2921211FF32E0010B4BA /* TableViewCells.swift */, 032A15E125965E1100E00FE3 /* CameraInputBarAccessoryView.swift */, + 9B49D541263DA6F9008804B5 /* CustomMessageContentCell.swift */, + 9B49D546263DAA29008804B5 /* CustomTextMessageContentCell.swift */, ); path = Views; sourceTree = ""; @@ -162,6 +174,8 @@ 385C2926211FF33B0010B4BA /* MockMessage.swift */, 385C2925211FF33A0010B4BA /* MockSocket.swift */, 383B9EB22172A1C4008AB91A /* MockUser.swift */, + 9B49D52C263C3E1F008804B5 /* CustomTextLayoutSizeCalculator.swift */, + 9B49D539263D9606008804B5 /* CustomLayoutSizeCalculator.swift */, ); path = Models; sourceTree = ""; @@ -207,6 +221,7 @@ 385C293C211FF38E0010B4BA /* SettingsViewController.swift */, 63ECDABD24182A470016C349 /* MessageSubviewViewController.swift */, 63ECDAC1241889630016C349 /* MessageSubviewContainerViewController.swift */, + 9B49D527263C31FC008804B5 /* CustomLayoutExampleViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -508,8 +523,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9B49D53A263D9606008804B5 /* CustomLayoutSizeCalculator.swift in Sources */, 385C2922211FF32E0010B4BA /* CustomCell.swift in Sources */, + 9B49D542263DA6F9008804B5 /* CustomMessageContentCell.swift in Sources */, 385C2946211FF38F0010B4BA /* LaunchViewController.swift in Sources */, + 9B49D52D263C3E1F008804B5 /* CustomTextLayoutSizeCalculator.swift in Sources */, 1C5433E224C3A33600A5383B /* SwiftUIExampleView.swift in Sources */, 385C2939211FF37B0010B4BA /* Lorem.swift in Sources */, 385C2928211FF33B0010B4BA /* MockMessage.swift in Sources */, @@ -522,6 +540,8 @@ 385C2932211FF3630010B4BA /* UIViewController+Extensions.swift in Sources */, 385C292B211FF3450010B4BA /* Settings+UserDefaults.swift in Sources */, 385C2945211FF38F0010B4BA /* NavigationController.swift in Sources */, + 9B49D547263DAA29008804B5 /* CustomTextMessageContentCell.swift in Sources */, + 9B49D528263C31FC008804B5 /* CustomLayoutExampleViewController.swift in Sources */, 032A15DD25965D9A00E00FE3 /* AlertService.swift in Sources */, 385C2948211FF38F0010B4BA /* ChatViewController.swift in Sources */, 385C2944211FF38F0010B4BA /* MessageContainerController.swift in Sources */, diff --git a/Example/Sources/Models/CustomLayoutSizeCalculator.swift b/Example/Sources/Models/CustomLayoutSizeCalculator.swift new file mode 100644 index 00000000..2034558c --- /dev/null +++ b/Example/Sources/Models/CustomLayoutSizeCalculator.swift @@ -0,0 +1,160 @@ +// +// CustomLayoutSizeCalculator.swift +// ChatExample +// +// Created by Vignesh J on 01/05/21. +// Copyright © 2021 MessageKit. All rights reserved. +// + +import UIKit +import MessageKit + +class CustomLayoutSizeCalculator: CellSizeCalculator { + + var cellTopLabelVerticalPadding: CGFloat = 32 + var cellTopLabelHorizontalPadding: CGFloat = 32 + var cellMessageContainerHorizontalPadding: CGFloat = 48 + var cellMessageContainerExtraSpacing: CGFloat = 16 + var cellMessageContentVerticalPadding: CGFloat = 16 + var cellMessageContentHorizontalPadding: CGFloat = 16 + var cellDateLabelHorizontalPadding: CGFloat = 24 + var cellDateLabelBottomPadding: CGFloat = 8 + + var messagesLayout: MessagesCollectionViewFlowLayout { + self.layout as! MessagesCollectionViewFlowLayout + } + + var messageContainerMaxWidth: CGFloat { + self.messagesLayout.itemWidth - + self.cellMessageContainerHorizontalPadding - + self.cellMessageContainerExtraSpacing + } + + var messagesDataSource: MessagesDataSource { + self.messagesLayout.messagesDataSource + } + + init(layout: MessagesCollectionViewFlowLayout? = nil) { + super.init() + + self.layout = layout + } + + override func sizeForItem(at indexPath: IndexPath) -> CGSize { + let dataSource = self.messagesDataSource + let message = dataSource.messageForItem(at: indexPath, + in: self.messagesLayout.messagesCollectionView) + let itemHeight = self.cellContentHeight(for: message, + at: indexPath) + return CGSize(width: self.messagesLayout.itemWidth, + height: itemHeight) + } + + func cellContentHeight(for message: MessageType, + at indexPath: IndexPath) -> CGFloat { + self.cellTopLabelSize(for: message, + at: indexPath).height + + self.cellMessageBottomLabelSize(for: message, + at: indexPath).height + + self.messageContainerSize(for: message, + at: indexPath).height + } + + // MARK: - Top cell Label + + func cellTopLabelSize(for message: MessageType, + at indexPath: IndexPath) -> CGSize { + guard let attributedText = self.messagesDataSource.cellTopLabelAttributedText(for: message, + at: indexPath) else { + return .zero + } + + let maxWidth = self.messagesLayout.itemWidth - self.cellTopLabelHorizontalPadding + let size = attributedText.size(consideringWidth: maxWidth) + let height = size.height + self.cellTopLabelVerticalPadding + + return CGSize(width: maxWidth, + height: height) + } + + func cellTopLabelFrame(for message: MessageType, + at indexPath: IndexPath) -> CGRect { + let size = self.cellTopLabelSize(for: message, + at: indexPath) + guard size != .zero else { + return .zero + } + + let origin = CGPoint(x: self.cellTopLabelHorizontalPadding / 2, + y: 0) + + + return CGRect(origin: origin, + size: size) + } + + func cellMessageBottomLabelSize(for message: MessageType, + at indexPath: IndexPath) -> CGSize { + guard let attributedText = self.messagesDataSource.messageBottomLabelAttributedText(for: message, + at: indexPath) else { + return .zero + } + let maxWidth = self.messageContainerMaxWidth - self.cellDateLabelHorizontalPadding + + return attributedText.size(consideringWidth: maxWidth) + } + + func cellMessageBottomLabelFrame(for message: MessageType, + at indexPath: IndexPath) -> CGRect { + let messageContainerSize = self.messageContainerSize(for: message, + at: indexPath) + let labelSize = self.cellMessageBottomLabelSize(for: message, + at: indexPath) + let x = messageContainerSize.width - labelSize.width - (self.cellDateLabelHorizontalPadding / 2) + let y = messageContainerSize.height - labelSize.height - self.cellDateLabelBottomPadding + let origin = CGPoint(x: x, + y: y) + + return CGRect(origin: origin, + size: labelSize) + } + + // MARK: - MessageContainer + + func messageContainerSize(for message: MessageType, + at indexPath: IndexPath) -> CGSize { + let labelSize = self.cellMessageBottomLabelSize(for: message, + at: indexPath) + let width = labelSize.width + + self.cellMessageContentHorizontalPadding + + self.cellDateLabelHorizontalPadding + let height = labelSize.height + + self.cellMessageContentVerticalPadding + + self.cellDateLabelBottomPadding + + return CGSize(width: width, + height: height) + } + + func messageContainerFrame(for message: MessageType, + at indexPath: IndexPath, + fromCurrentSender: Bool) -> CGRect { + let y = self.cellTopLabelSize(for: message, + at: indexPath).height + let size = self.messageContainerSize(for: message, + at: indexPath) + let origin: CGPoint + if fromCurrentSender { + let x = self.messagesLayout.itemWidth - + size.width - + (self.cellMessageContainerHorizontalPadding / 2) + origin = CGPoint(x: x, y: y) + } else { + origin = CGPoint(x: self.cellMessageContainerHorizontalPadding / 2, + y: y) + } + + return CGRect(origin: origin, + size: size) + } +} diff --git a/Example/Sources/Models/CustomTextLayoutSizeCalculator.swift b/Example/Sources/Models/CustomTextLayoutSizeCalculator.swift new file mode 100644 index 00000000..17381137 --- /dev/null +++ b/Example/Sources/Models/CustomTextLayoutSizeCalculator.swift @@ -0,0 +1,64 @@ +// +// CustomTextMessageSizeCalculator.swift +// ChatExample +// +// Created by Vignesh J on 30/04/21. +// Copyright © 2021 MessageKit. All rights reserved. +// + +import UIKit +import MessageKit + +class CustomTextLayoutSizeCalculator: CustomLayoutSizeCalculator { + + var messageLabelFont = UIFont.preferredFont(forTextStyle: .body) + var cellMessageContainerRightSpacing: CGFloat = 16 + + override func messageContainerSize(for message: MessageType, + at indexPath: IndexPath) -> CGSize { + let size = super.messageContainerSize(for: message, + at: indexPath) + let labelSize = self.messageLabelSize(for: message, + at: indexPath) + let selfWidth = labelSize.width + + self.cellMessageContentHorizontalPadding + + self.cellMessageContainerRightSpacing + let width = max(selfWidth, size.width) + let height = size.height + labelSize.height + + return CGSize(width: width, + height: height) + } + + func messageLabelSize(for message: MessageType, + at indexPath: IndexPath) -> CGSize { + let attributedText: NSAttributedString + + let textMessageKind = message.kind + switch textMessageKind { + case .attributedText(let text): + attributedText = text + case .text(let text), .emoji(let text): + attributedText = NSAttributedString(string: text, attributes: [.font: messageLabelFont]) + default: + fatalError("messageLabelSize received unhandled MessageDataType: \(message.kind)") + } + + let maxWidth = self.messageContainerMaxWidth - + self.cellMessageContentHorizontalPadding - + self.cellMessageContainerRightSpacing + + return attributedText.size(consideringWidth: maxWidth) + } + + func messageLabelFrame(for message: MessageType, + at indexPath: IndexPath) -> CGRect { + let origin = CGPoint(x: self.cellMessageContentHorizontalPadding / 2, + y: self.cellMessageContentVerticalPadding / 2) + let size = self.messageLabelSize(for: message, + at: indexPath) + + return CGRect(origin: origin, + size: size) + } +} diff --git a/Example/Sources/View Controllers/BasicExampleViewController.swift b/Example/Sources/View Controllers/BasicExampleViewController.swift index d43cb8d4..875395a0 100644 --- a/Example/Sources/View Controllers/BasicExampleViewController.swift +++ b/Example/Sources/View Controllers/BasicExampleViewController.swift @@ -33,6 +33,10 @@ class BasicExampleViewController: ChatViewController { messagesCollectionView.messagesLayoutDelegate = self messagesCollectionView.messagesDisplayDelegate = self } + + func textCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return nil + } } // MARK: - MessagesDisplayDelegate @@ -136,5 +140,4 @@ extension BasicExampleViewController: MessagesLayoutDelegate { func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { return 16 } - } diff --git a/Example/Sources/View Controllers/ChatViewController.swift b/Example/Sources/View Controllers/ChatViewController.swift index b5e71b8b..c29488a5 100644 --- a/Example/Sources/View Controllers/ChatViewController.swift +++ b/Example/Sources/View Controllers/ChatViewController.swift @@ -188,6 +188,10 @@ class ChatViewController: MessagesViewController, MessagesDataSource { let dateString = formatter.string(from: message.sentDate) return NSAttributedString(string: dateString, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)]) } + + func textCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + return nil + } } // MARK: - MessageCellDelegate diff --git a/Example/Sources/View Controllers/CustomLayoutExampleViewController.swift b/Example/Sources/View Controllers/CustomLayoutExampleViewController.swift new file mode 100644 index 00000000..94de539c --- /dev/null +++ b/Example/Sources/View Controllers/CustomLayoutExampleViewController.swift @@ -0,0 +1,45 @@ +// +// CustomLayoutExampleViewController.swift +// ChatExample +// +// Created by Vignesh J on 30/04/21. +// Copyright © 2021 MessageKit. All rights reserved. +// + +import UIKit +import MapKit +import MessageKit +import Kingfisher + +class CustomLayoutExampleViewController: BasicExampleViewController { + + private lazy var textMessageSizeCalculator: CustomTextLayoutSizeCalculator = CustomTextLayoutSizeCalculator(layout: self.messagesCollectionView.messagesCollectionViewFlowLayout) + + override func configureMessageCollectionView() { + super.configureMessageCollectionView() + self.messagesCollectionView.register(CustomTextMessageContentCell.self) + messagesCollectionView.messagesDataSource = self + messagesCollectionView.messagesLayoutDelegate = self + messagesCollectionView.messagesDisplayDelegate = self + } + + // MARK: - MessagesLayoutDelegate + + override func textCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator? { + return self.textMessageSizeCalculator + } + + // MARK: - MessagesDataSource + + override func textCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell? { + let cell = messagesCollectionView.dequeueReusableCell(CustomTextMessageContentCell.self, + for: indexPath) + cell.configure(with: message, + at: indexPath, + in: messagesCollectionView, + dataSource: self, + and: self.textMessageSizeCalculator) + + return cell + } +} diff --git a/Example/Sources/View Controllers/LaunchViewController.swift b/Example/Sources/View Controllers/LaunchViewController.swift index 55d110d8..5fe78017 100644 --- a/Example/Sources/View Controllers/LaunchViewController.swift +++ b/Example/Sources/View Controllers/LaunchViewController.swift @@ -33,7 +33,7 @@ final internal class LaunchViewController: UITableViewController { return .lightContent } - let cells = ["Basic Example", "Advanced Example", "Autocomplete Example", "Embedded Example", "Subview Example", "SwiftUI Example", "Settings", "Source Code", "Contributors"] + let cells = ["Basic Example", "Advanced Example", "Autocomplete Example", "Embedded Example", "Custom Layout Example", "Subview Example", "SwiftUI Example", "Settings", "Source Code", "Contributors"] // MARK: - View Life Cycle @@ -88,6 +88,8 @@ final internal class LaunchViewController: UITableViewController { splitViewController?.showDetailViewController(detailViewController, sender: self) case "Embedded Example": navigationController?.pushViewController(MessageContainerController(), animated: true) + case "Custom Layout Example": + navigationController?.pushViewController(CustomLayoutExampleViewController(), animated: true) case "SwiftUI Example": if #available(iOS 13, *) { navigationController?.pushViewController(UIHostingController(rootView: SwiftUIExampleView()), animated: true) diff --git a/Example/Sources/Views/CustomMessageContentCell.swift b/Example/Sources/Views/CustomMessageContentCell.swift new file mode 100644 index 00000000..cf6778b7 --- /dev/null +++ b/Example/Sources/Views/CustomMessageContentCell.swift @@ -0,0 +1,120 @@ +// +// CustomMessageContentCell.swift +// ChatExample +// +// Created by Vignesh J on 01/05/21. +// Copyright © 2021 MessageKit. All rights reserved. +// + +import UIKit +import MessageKit + +class CustomMessageContentCell: MessageCollectionViewCell { + + /// The `MessageCellDelegate` for the cell. + weak var delegate: MessageCellDelegate? + + /// The container used for styling and holding the message's content view. + var messageContainerView: UIView = { + let containerView = UIView() + containerView.clipsToBounds = true + containerView.layer.masksToBounds = true + return containerView + }() + + /// The top label of the cell. + var cellTopLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + return label + }() + + var cellDateLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .right + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.setupSubviews() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.setupSubviews() + } + + override func prepareForReuse() { + super.prepareForReuse() + self.cellTopLabel.text = nil + self.cellTopLabel.attributedText = nil + self.cellDateLabel.text = nil + self.cellDateLabel.attributedText = nil + } + + /// Handle tap gesture on contentView and its subviews. + override func handleTapGesture(_ gesture: UIGestureRecognizer) { + let touchLocation = gesture.location(in: self) + + switch true { + case self.messageContainerView.frame.contains(touchLocation) && !self.cellContentView(canHandle: convert(touchLocation, to: messageContainerView)): + self.delegate?.didTapMessage(in: self) + case self.cellTopLabel.frame.contains(touchLocation): + self.delegate?.didTapCellTopLabel(in: self) + case self.cellDateLabel.frame.contains(touchLocation): + self.delegate?.didTapMessageBottomLabel(in: self) + default: + self.delegate?.didTapBackground(in: self) + } + } + + /// Handle long press gesture, return true when gestureRecognizer's touch point in `messageContainerView`'s frame + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + let touchPoint = gestureRecognizer.location(in: self) + guard gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) else { return false } + return self.messageContainerView.frame.contains(touchPoint) + } + + func setupSubviews() { + self.messageContainerView.layer.cornerRadius = 5 + + self.contentView.addSubview(self.cellTopLabel) + self.contentView.addSubview(self.messageContainerView) + self.messageContainerView.addSubview(self.cellDateLabel) + } + + func configure(with message: MessageType, + at indexPath: IndexPath, + in messagesCollectionView: MessagesCollectionView, + dataSource: MessagesDataSource, + and sizeCalculator: CustomLayoutSizeCalculator) { + guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { + return + } + self.cellTopLabel.frame = sizeCalculator.cellTopLabelFrame(for: message, + at: indexPath) + self.cellDateLabel.frame = sizeCalculator.cellMessageBottomLabelFrame(for: message, + at: indexPath) + self.messageContainerView.frame = sizeCalculator.messageContainerFrame(for: message, + at: indexPath, + fromCurrentSender: dataSource.isFromCurrentSender(message: message)) + self.cellTopLabel.attributedText = dataSource.cellTopLabelAttributedText(for: message, + at: indexPath) + self.cellDateLabel.attributedText = dataSource.messageBottomLabelAttributedText(for: message, + at: indexPath) + self.messageContainerView.backgroundColor = displayDelegate.backgroundColor(for: message, + at: indexPath, + in: messagesCollectionView) + + } + + /// Handle `ContentView`'s tap gesture, return false when `ContentView` doesn't needs to handle gesture + func cellContentView(canHandle touchPoint: CGPoint) -> Bool { + false + } +} diff --git a/Example/Sources/Views/CustomTextMessageContentCell.swift b/Example/Sources/Views/CustomTextMessageContentCell.swift new file mode 100644 index 00000000..083cf4b5 --- /dev/null +++ b/Example/Sources/Views/CustomTextMessageContentCell.swift @@ -0,0 +1,67 @@ +// +// CustomTextMessageContentCell.swift +// ChatExample +// +// Created by Vignesh J on 01/05/21. +// Copyright © 2021 MessageKit. All rights reserved. +// + +import UIKit +import MessageKit + +class CustomTextMessageContentCell: CustomMessageContentCell { + + /// The label used to display the message's text. + var messageLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.font = UIFont.preferredFont(forTextStyle: .body) + + return label + }() + + override func prepareForReuse() { + super.prepareForReuse() + + self.messageLabel.attributedText = nil + self.messageLabel.text = nil + } + + override func setupSubviews() { + super.setupSubviews() + + self.messageContainerView.addSubview(self.messageLabel) + } + + override func configure(with message: MessageType, + at indexPath: IndexPath, + in messagesCollectionView: MessagesCollectionView, + dataSource: MessagesDataSource, + and sizeCalculator: CustomLayoutSizeCalculator) { + super.configure(with: message, + at: indexPath, + in: messagesCollectionView, + dataSource: dataSource, + and: sizeCalculator) + + guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { + return + } + + let calculator = sizeCalculator as? CustomTextLayoutSizeCalculator + self.messageLabel.frame = calculator?.messageLabelFrame(for: message, + at: indexPath) ?? .zero + + let textMessageKind = message.kind + switch textMessageKind { + case .text(let text), .emoji(let text): + let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView) + messageLabel.text = text + messageLabel.textColor = textColor + case .attributedText(let text): + messageLabel.attributedText = text + default: + break + } + } +} diff --git a/Sources/Extensions/NSAttributedString+Extensions.swift b/Sources/Extensions/NSAttributedString+Extensions.swift index 7ac6e4bb..6377dc62 100644 --- a/Sources/Extensions/NSAttributedString+Extensions.swift +++ b/Sources/Extensions/NSAttributedString+Extensions.swift @@ -25,13 +25,40 @@ import Foundation import UIKit -internal extension NSAttributedString { +public extension NSAttributedString { func width(considering height: CGFloat) -> CGFloat { + let size = self.size(consideringHeight: height) + return size.width + + } + + func height(considering width: CGFloat) -> CGFloat { + + let size = self.size(consideringWidth: width) + return size.height + + } + + func size(consideringHeight height: CGFloat) -> CGSize { + let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height) - let rect = self.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil) - return rect.width + return self.size(considering: constraintBox) + + } + + func size(consideringWidth width: CGFloat) -> CGSize { + + let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude) + return self.size(considering: constraintBox) + + } + + func size(considering size: CGSize) -> CGSize { + + let rect = self.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil) + return rect.size } }