diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 495f2d3d..8c1d63f7 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -346,7 +346,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 17575422C6ED250D743DD40C /* [CP] Embed Pods Frameworks */ = { @@ -379,7 +379,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 1E9549B97552A057E870E4D4 /* [CP] Embed Pods Frameworks */ = { @@ -445,7 +445,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 57DC8C6D452F2FB13B25D6AA /* [CP] Copy Pods Resources */ = { diff --git a/Example/Sources/MockMessage.swift b/Example/Sources/MockMessage.swift index f725f7dc..7fa8165a 100644 --- a/Example/Sources/MockMessage.swift +++ b/Example/Sources/MockMessage.swift @@ -61,4 +61,8 @@ struct MockMessage: MessageType { self.init(data: .location(location), sender: sender, messageId: messageId, date: date) } + init(emoji: String, sender: Sender, messageId: String, date: Date) { + self.init(data: .emoji(emoji), sender: sender, messageId: messageId, date: date) + } + } diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index 72e4e1f1..d6eb14de 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -70,7 +70,7 @@ final class SampleData { var now = Date() - let messageTypes = ["Text", "Text", "Text", "AttributedText", "Photo", "Video", "Location"] + let messageTypes = ["Text", "Text", "Text", "AttributedText", "Photo", "Video", "Location", "Emoji"] let attributes = ["Font1", "Font2", "Font3", "Font4", "Color", "Combo"] @@ -83,6 +83,15 @@ final class SampleData { CLLocation(latitude: 39.3218, longitude: -113.3317) ] + let emojis = [ + "👍", + "👋", + "👋👋👋", + "😱😱", + "🎈", + "🇧🇷", + ] + func attributedString(with text: String) -> NSAttributedString { let nsString = NSString(string: text) var mutableAttributedString = NSMutableAttributedString(string: text) @@ -139,6 +148,7 @@ final class SampleData { let randomNumberImage = Int(arc4random_uniform(UInt32(messageImages.count))) let randomMessageType = Int(arc4random_uniform(UInt32(messageTypes.count))) let randomNumberLocation = Int(arc4random_uniform(UInt32(locations.count))) + let randomNumberEmoji = Int(arc4random_uniform(UInt32(emojis.count))) let uniqueID = NSUUID().uuidString let sender = senders[randomNumberSender] let date = dateAddingRandomTime() @@ -157,6 +167,8 @@ final class SampleData { return MockMessage(thumbnail: image, sender: sender, messageId: uniqueID, date: date) case "Location": return MockMessage(location: locations[randomNumberLocation], sender: sender, messageId: uniqueID, date: date) + case "Emoji": + return MockMessage(emoji: emojis[randomNumberEmoji], sender: sender, messageId: uniqueID, date: date) default: fatalError("Unrecognized mock message type") } diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index e7bd42fa..7c5033f1 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 38C8679A1F50EA1000811974 /* NSConstraintLayoutSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */; }; 38C8679B1F50EA1000811974 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867991F50EA1000811974 /* UIView+Extensions.swift */; }; 38C8679E1F50EA4A00811974 /* InputBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C8679D1F50EA4A00811974 /* InputBarItem.swift */; }; + 492CB9F11F84362A009FE895 /* EmojiMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492CB9F01F84362A009FE895 /* EmojiMessageCell.swift */; }; 7E7555A81F75C819004C50B6 /* LocationMessageDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7555A71F75C819004C50B6 /* LocationMessageDisplayDelegate.swift */; }; 882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882D75831DE507320033F95F /* MessagesDataSource.swift */; }; 888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888CEBFB1D3FD525005178DE /* MessagesViewController.swift */; }; @@ -83,6 +84,7 @@ 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSConstraintLayoutSet.swift; sourceTree = ""; }; 38C867991F50EA1000811974 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; 38C8679D1F50EA4A00811974 /* InputBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputBarItem.swift; sourceTree = ""; }; + 492CB9F01F84362A009FE895 /* EmojiMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiMessageCell.swift; sourceTree = ""; }; 7E7555A71F75C819004C50B6 /* LocationMessageDisplayDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMessageDisplayDelegate.swift; sourceTree = ""; }; 882D75831DE507320033F95F /* MessagesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDataSource.swift; sourceTree = ""; }; 888CEBFB1D3FD525005178DE /* MessagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = ""; }; @@ -258,6 +260,7 @@ E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */, B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */, B0291DA81F6DBB9F00BEDF03 /* TextMessageCell.swift */, + 492CB9F01F84362A009FE895 /* EmojiMessageCell.swift */, B0D943BD1F6DC9AB008B7BFD /* MediaMessageCell.swift */, B010490F1F6F8684008DBA58 /* PlayButtonView.swift */, B01049111F6F86E8008DBA58 /* LocationMessageCell.swift */, @@ -478,6 +481,7 @@ 88916B471CF0DFE600469F91 /* MessageType.swift in Sources */, B0D943BE1F6DC9AB008B7BFD /* MediaMessageCell.swift in Sources */, B0291DA91F6DBB9F00BEDF03 /* TextMessageCell.swift in Sources */, + 492CB9F11F84362A009FE895 /* EmojiMessageCell.swift in Sources */, B0AA1F511F42E91A00BAE583 /* AvatarAlignment.swift in Sources */, B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */, 37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */, diff --git a/Sources/EmojiMessageCell.swift b/Sources/EmojiMessageCell.swift new file mode 100644 index 00000000..e5f7f18a --- /dev/null +++ b/Sources/EmojiMessageCell.swift @@ -0,0 +1,54 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import UIKit + +class EmojiMessageCell: TextMessageCell { + + // MARK: - Methods + open override func prepareForReuse() { + messageContainerView.backgroundColor = .clear + } + + open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { + super.apply(layoutAttributes) + + guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else { return } + messageContentView.font = attributes.emojiLabelFont + } + + open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + super.configure(with: message, at: indexPath, and: messagesCollectionView) + + messageContainerView.backgroundColor = .clear + + switch message.data { + case .emoji(let text): + messageContentView.text = text + default: + break + } + } + +} diff --git a/Sources/MessageData.swift b/Sources/MessageData.swift index 3e677278..ee4ff102 100644 --- a/Sources/MessageData.swift +++ b/Sources/MessageData.swift @@ -32,6 +32,7 @@ public enum MessageData { case photo(UIImage) case video(file: URL, thumbnail: UIImage) case location(CLLocation) + case emoji(String) // MARK: - Not supported yet diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 1092f3ec..3e97a6f1 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -30,6 +30,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { // MARK: - Properties open var messageLabelFont: UIFont + open var emojiLabelFont: UIFont + open var messageToViewEdgePadding: CGFloat open var avatarAlwaysLeading: Bool { @@ -68,6 +70,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { public override init() { messageLabelFont = UIFont.preferredFont(forTextStyle: .body) + emojiLabelFont = messageLabelFont.withSize(2 * messageLabelFont.pointSize) + messageToViewEdgePadding = 30.0 avatarAlwaysLeading = false @@ -309,6 +313,10 @@ extension MessagesCollectionViewFlowLayout { 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 diff --git a/Sources/MessagesCollectionViewLayoutAttributes.swift b/Sources/MessagesCollectionViewLayoutAttributes.swift index 7bd823ac..b36e3262 100644 --- a/Sources/MessagesCollectionViewLayoutAttributes.swift +++ b/Sources/MessagesCollectionViewLayoutAttributes.swift @@ -33,6 +33,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib var messageContainerFrame: CGRect = .zero var messageLabelFont: UIFont = UIFont.preferredFont(forTextStyle: .body) + lazy var emojiLabelFont = messageLabelFont.withSize(2 * messageLabelFont.pointSize) var messageLabelInsets: UIEdgeInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 14) var cellTopLabelFrame: CGRect = .zero diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index da2d6177..728138b1 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -116,6 +116,9 @@ open class MessagesViewController: UIViewController { messagesCollectionView.register(LocationMessageCell.self, forCellWithReuseIdentifier: "LocationMessageCell") + messagesCollectionView.register(EmojiMessageCell.self, + forCellWithReuseIdentifier: "EmojiMessageCell") + messagesCollectionView.register(MessageFooterView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "MessageFooterView") @@ -203,6 +206,12 @@ extension MessagesViewController: UICollectionViewDataSource { } cell.configure(with: message, at: indexPath, and: messagesCollectionView) return cell + case .emoji: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "EmojiMessageCell", for: indexPath) as? EmojiMessageCell else { + fatalError("Unable to dequeue EmojiMessageCell") + } + cell.configure(with: message, at: indexPath, and: messagesCollectionView) + return cell } }