diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full.png b/Assets/MessageKitAssets.bundle/Images/bubble_full.png
new file mode 100644
index 00000000..69a0ca0c
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full@2x.png
new file mode 100644
index 00000000..22a7c7a0
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full@2x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full@3x.png
new file mode 100644
index 00000000..0cb79566
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full@3x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1.png
new file mode 100644
index 00000000..d5023848
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@2x.png
new file mode 100644
index 00000000..b5d49019
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@2x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@3x.png
new file mode 100644
index 00000000..96990dc4
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@3x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2.png
new file mode 100644
index 00000000..71a86a56
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@2x.png
new file mode 100644
index 00000000..4d837f95
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@2x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@3x.png
new file mode 100644
index 00000000..afd826d0
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@3x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined.png
new file mode 100644
index 00000000..91ba9a2f
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined@2x.png
new file mode 100644
index 00000000..47ac6516
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined@2x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined@3x.png
new file mode 100644
index 00000000..4f5b75db
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined@3x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1.png
new file mode 100644
index 00000000..c347d971
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@2x.png
new file mode 100644
index 00000000..621545ee
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@2x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@3x.png
new file mode 100644
index 00000000..570e501b
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@3x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2.png
new file mode 100644
index 00000000..260e86aa
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@2x.png
new file mode 100644
index 00000000..7eeb8abe
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@2x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@3x.png
new file mode 100644
index 00000000..7924f642
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@3x.png differ
diff --git a/Assets/MessageKitAssets.bundle/Root.plist b/Assets/MessageKitAssets.bundle/Root.plist
new file mode 100644
index 00000000..b1b6fea5
--- /dev/null
+++ b/Assets/MessageKitAssets.bundle/Root.plist
@@ -0,0 +1,61 @@
+
+
+
+
+ StringsTable
+ Root
+ PreferenceSpecifiers
+
+
+ Type
+ PSGroupSpecifier
+ Title
+ Group
+
+
+ Type
+ PSTextFieldSpecifier
+ Title
+ Name
+ Key
+ name_preference
+ DefaultValue
+
+ IsSecure
+
+ KeyboardType
+ Alphabet
+ AutocapitalizationType
+ None
+ AutocorrectionType
+ No
+
+
+ Type
+ PSToggleSwitchSpecifier
+ Title
+ Enabled
+ Key
+ enabled_preference
+ DefaultValue
+
+
+
+ Type
+ PSSliderSpecifier
+ Key
+ slider_preference
+ DefaultValue
+ 0.5
+ MinimumValue
+ 0
+ MaximumValue
+ 1
+ MinimumValueImage
+
+ MaximumValueImage
+
+
+
+
+
diff --git a/Assets/MessageKitAssets.bundle/en.lproj/Root.strings b/Assets/MessageKitAssets.bundle/en.lproj/Root.strings
new file mode 100644
index 00000000..8cd87b9d
Binary files /dev/null and b/Assets/MessageKitAssets.bundle/en.lproj/Root.strings differ
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a7d855b..00b91e32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,9 +7,13 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa
## Upcoming release
----------------
+## [Prerelease] 0.7.0
+
+This release closes the [0.7 milestone](https://github.com/MessageKit/MessageKit/milestone/8?closed=1)
+
## [Prerelease] 0.6.0
-This release closes the [0.6 milestone](https://github.com/MessageKit/MessageKit/milestone/6?closed=1).
+This release closes the [0.6 milestone](https://github.com/MessageKit/MessageKit/milestone/7?closed=1).
## [Prerelease] 0.5.0
diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj
index 4b2018e7..6ffd22e3 100644
--- a/Example/ChatExample.xcodeproj/project.pbxproj
+++ b/Example/ChatExample.xcodeproj/project.pbxproj
@@ -450,7 +450,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -493,7 +493,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_VERSION = 3.0;
@@ -507,6 +507,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -521,6 +522,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift
index 49308a08..37a14381 100644
--- a/Example/Sources/ConversationViewController.swift
+++ b/Example/Sources/ConversationViewController.swift
@@ -35,12 +35,11 @@ class ConversationViewController: MessagesViewController {
messageList = SampleData().getMessages()
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
-
+ messagesCollectionView.messagesDisplayDelegate = self
messagesCollectionView.messageCellDelegate = self
- messagesCollectionView.messageLabelDelegate = self
messageInputBar.delegate = self
-
- setupInputBar()
+
+ //setupInputBar()
}
func setupInputBar() {
@@ -130,28 +129,10 @@ extension ConversationViewController: MessagesDataSource {
return messageList[indexPath.section]
}
-}
-
-// MARK: - MessagesDisplayDataSource
-
-extension ConversationViewController: MessagesDisplayDataSource {
-
func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
return SampleData().getAvatarFor(sender: message.sender)
}
- func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
- return .messageTop
- }
-
- func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? {
- return messagesCollectionView.dequeueMessageHeaderView(for: indexPath)
- }
-
- func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? {
- return messagesCollectionView.dequeueMessageFooterView(for: indexPath)
- }
-
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let name = message.sender.displayName
return NSAttributedString(string: name, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption1)])
@@ -166,16 +147,36 @@ extension ConversationViewController: MessagesDisplayDataSource {
}
+// MARK: - MessagesDisplayDelegate
+
+extension ConversationViewController: MessagesDisplayDelegate {
+
+ func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
+ return isFromCurrentSender(message: message) ? .white : .darkText
+ }
+
+ func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
+ let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft
+ return .bubbleTail(corner, .curved)
+ }
+
+ func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? {
+ return messagesCollectionView.dequeueMessageFooterView(for: indexPath)
+ }
+
+}
+
// MARK: - MessagesLayoutDelegate
extension ConversationViewController: MessagesLayoutDelegate {
- func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
- return CGSize(width: messagesCollectionView.bounds.width, height: 4)
+ func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment {
+ return .messageBottom
}
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
- return CGSize(width: messagesCollectionView.bounds.width, height: 4)
+
+ return CGSize(width: messagesCollectionView.bounds.width, height: 10)
}
}
diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift
index 8e022bbd..3ab63229 100644
--- a/Example/Sources/SampleData.swift
+++ b/Example/Sources/SampleData.swift
@@ -31,49 +31,54 @@ struct SampleData {
let Cook = Sender(id: "656361", displayName: "Tim Cook")
func getMessages() -> [MockMessage] {
+
let msg1 = MockMessage(text: "Check out this awesome UI library for Chat", sender: Dan, messageId: UUID().uuidString)
- let msg2 = MockMessage(text: "This is insane.", sender: Steven, messageId: UUID().uuidString)
- let msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Cook, messageId: UUID().uuidString)
- let msg4 = MockMessage(text: "My favorite things in life don’t cost any money. It’s really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString)
- let msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Cook, messageId: UUID().uuidString)
- let msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.", sender: Jobs, messageId: UUID().uuidString)
- let msg7 = MockMessage(text: "Remembering that I'll be dead soon is the most important tool I've ever encountered to help me make the big choices in life. Because almost everything - all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important.", sender: Jobs, messageId: UUID().uuidString)
- let msg8 = MockMessage(text: "Price is rarely the most important thing. A cheap product might sell some units. Somebody gets it home and they feel great when they pay the money, but then they get it home and use it and the joy is gone.", sender: Cook, messageId: UUID().uuidString)
+ var msg2 = MockMessage(text: "This is insane.", sender: Steven, messageId: UUID().uuidString)
+ var msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Dan, messageId: UUID().uuidString)
+ var msg4 = MockMessage(text: "My favorite things in life don’t cost any money. It’s really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString)
+ var msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Dan, messageId: UUID().uuidString)
+ var msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.", sender: Jobs, messageId: UUID().uuidString)
+ var msg7 = MockMessage(text: "Remembering that I'll be dead soon is the most important tool I've ever encountered to help me make the big choices in life. Because almost everything - all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important.", sender: Dan, messageId: UUID().uuidString)
+ var msg8 = MockMessage(text: "Price is rarely the most important thing. A cheap product might sell some units. Somebody gets it home and they feel great when they pay the money, but then they get it home and use it and the joy is gone.", sender: Cook, messageId: UUID().uuidString)
let msg9String = "Use .attributedText() to add bold, italic, colored text and more..."
let msg9Text = NSString(string: msg9String)
let msg9AttributedText = NSMutableAttributedString(string: String(msg9Text))
- msg9AttributedText.addAttribute(NSFontAttributeName, value: UIFont.preferredFont(forTextStyle: .body), range: NSRange(location: 0, length: msg9Text.length))
-
- if #available(iOS 9.0, *) {
- msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: UIFontWeightBold)], range: msg9Text.range(of: ".attributedText()"))
- } else {
- msg9AttributedText.addAttributes([NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle], range: msg9Text.range(of: ".attributedText()"))
- }
-
- msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)],
- range: msg9Text.range(of: "bold"))
-
- msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)],
- range: msg9Text.range(of: "italic"))
-
- msg9AttributedText.addAttributes([NSForegroundColorAttributeName: UIColor.red],
- range: msg9Text.range(of: "colored"))
-
- let msg9 = MockMessage(attributedText: msg9AttributedText, sender: Jobs, messageId: UUID().uuidString)
- let msg10 = MockMessage(text: "1-800-555-0000", sender: Steven, messageId: UUID().uuidString)
- let msg11 = MockMessage(text: "One Infinite Loop Cupertino, CA 95014 This is some extra text that should not be detected.", sender: Cook, messageId: UUID().uuidString)
- let msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Steven, messageId: UUID().uuidString)
- let msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString)
+ msg9AttributedText.addAttribute(NSFontAttributeName, value: UIFont.preferredFont(forTextStyle: .body), range: NSRange(location: 0, length: msg9Text.length))
+ msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: UIFontWeightBold)], range: msg9Text.range(of: ".attributedText()"))
+ msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)], range: msg9Text.range(of: "bold"))
+ msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)], range: msg9Text.range(of: "italic"))
+ msg9AttributedText.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: msg9Text.range(of: "colored"))
- return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13]
+ var msg9 = MockMessage(attributedText: msg9AttributedText, sender: Jobs, messageId: UUID().uuidString)
+ var msg10 = MockMessage(text: "1-800-555-0000", sender: Dan, messageId: UUID().uuidString)
+ var msg11 = MockMessage(text: "One Infinite Loop Cupertino, CA 95014 This is some extra text that should not be detected.", sender: Cook, messageId: UUID().uuidString)
+ let msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Dan, messageId: UUID().uuidString)
+ let msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString)
+
+ msg2.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg1.sentDate)!
+ msg3.sentDate = Calendar.current.date(byAdding: .minute, value: 37, to: msg2.sentDate)!
+ msg4.sentDate = Calendar.current.date(byAdding: .minute, value: 3, to: msg3.sentDate)!
+ msg5.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg4.sentDate)!
+ msg6.sentDate = Calendar.current.date(byAdding: .minute, value: 12, to: msg5.sentDate)!
+ msg7.sentDate = Calendar.current.date(byAdding: .minute, value: 23, to: msg6.sentDate)!
+ msg8.sentDate = Calendar.current.date(byAdding: .hour, value: 300, to: msg7.sentDate)!
+ msg9.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg8.sentDate)!
+ msg10.sentDate = Calendar.current.date(byAdding: .minute, value: 59, to: msg9.sentDate)!
+ msg11.sentDate = Calendar.current.date(byAdding: .hour, value: 7, to: msg10.sentDate)!
+
+ return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13].map { msg -> MockMessage in
+ var msg = msg
+ msg.sender = msg.sender == Dan ? Steven : Dan
+ return msg
+ }
}
-
+
func getCurrentSender() -> Sender {
return Dan
}
-
+
func getAvatarFor(sender: Sender) -> Avatar {
switch sender {
case Dan:
diff --git a/MessageKit.podspec b/MessageKit.podspec
index 6e7c8057..cd9236bf 100644
--- a/MessageKit.podspec
+++ b/MessageKit.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'MessageKit'
- s.version = '0.6.0'
+ s.version = '0.7.0'
s.license = { :type => "MIT", :file => "LICENSE.md" }
s.summary = 'An elegant messages UI library for iOS.'
diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj
index a6cae3fe..d5422460 100644
--- a/MessageKit.xcodeproj/project.pbxproj
+++ b/MessageKit.xcodeproj/project.pbxproj
@@ -15,7 +15,7 @@
376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376AD1871F4259D20083072A /* TestMessagesViewControllerModel.swift */; };
37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C936971F38F6AC00853DF2 /* Avatar.swift */; };
38C8679A1F50EA1000811974 /* NSConstraintLayoutSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */; };
- 38C8679B1F50EA1000811974 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867991F50EA1000811974 /* UIView+AutoLayout.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 */; };
882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882D75831DE507320033F95F /* MessagesDataSource.swift */; };
888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888CEBFB1D3FD525005178DE /* MessagesViewController.swift */; };
@@ -24,11 +24,17 @@
88916B451CF0DF5900469F91 /* MessageKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88916B431CF0DF5900469F91 /* MessageKitTests.swift */; };
88916B471CF0DFE600469F91 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88916B461CF0DFE600469F91 /* MessageType.swift */; };
B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */; };
+ B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */; };
+ B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */; };
+ B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; };
+ B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C951F600E290035B36E /* MessageKit+Availability.swift */; };
+ B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C971F61AF910035B36E /* LabelAlignment.swift */; };
B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; };
B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; };
+ B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; };
+ B01E2DDB1F5BC10000E4FA1C /* MessageKitAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */; };
B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */; };
B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05530B41F493CFA008BB420 /* DetectorType.swift */; };
- B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A271F23D71400542A83 /* MessageDirection.swift */; };
B0655A2A1F23D77200542A83 /* Sender.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A291F23D77200542A83 /* Sender.swift */; };
B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A2B1F23D81600542A83 /* MessageData.swift */; };
B0655A2E1F23D8BC00542A83 /* MessagesCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */; };
@@ -40,10 +46,12 @@
B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */; };
B074EEA81F3971A600ABB8C8 /* MessageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */; };
B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09643851F286C9E004D0129 /* String+Extensions.swift */; };
- B096438E1F2890FB004D0129 /* MessagesDisplayDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */; };
+ B096438E1F2890FB004D0129 /* MessagesDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */; };
B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438F1F289142004D0129 /* UIColor+Extensions.swift */; };
- B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */; };
+ B0AA1F511F42E91A00BAE583 /* AvatarAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */; };
B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */; };
+ B0E1756F1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */; };
+ E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -65,7 +73,7 @@
376AD1871F4259D20083072A /* TestMessagesViewControllerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestMessagesViewControllerModel.swift; sourceTree = ""; };
37C936971F38F6AC00853DF2 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = ""; };
38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSConstraintLayoutSet.swift; sourceTree = ""; };
- 38C867991F50EA1000811974 /* UIView+AutoLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayout.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 = ""; };
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 = ""; };
@@ -77,11 +85,17 @@
88916B431CF0DF5900469F91 /* MessageKitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitTests.swift; sourceTree = ""; };
88916B461CF0DFE600469F91 /* MessageType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = ""; };
B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = ""; };
+ B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; };
+ B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = ""; };
+ B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; };
+ B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = ""; };
+ B0147C971F61AF910035B36E /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = ""; };
B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; };
B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; };
+ B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; };
+ B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = MessageKitAssets.bundle; sourceTree = ""; };
B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellDelegate.swift; sourceTree = ""; };
B05530B41F493CFA008BB420 /* DetectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = ""; };
- B0655A271F23D71400542A83 /* MessageDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDirection.swift; sourceTree = ""; };
B0655A291F23D77200542A83 /* Sender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sender.swift; sourceTree = ""; };
B0655A2B1F23D81600542A83 /* MessageData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageData.swift; sourceTree = ""; };
B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionView.swift; sourceTree = ""; };
@@ -93,10 +107,12 @@
B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesLayoutDelegate.swift; sourceTree = ""; };
B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabel.swift; sourceTree = ""; };
B09643851F286C9E004D0129 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; };
- B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDataSource.swift; sourceTree = ""; };
+ B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDelegate.swift; sourceTree = ""; };
B096438F1F289142004D0129 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; };
- B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = ""; };
+ B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarAlignment.swift; sourceTree = ""; };
B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; };
+ B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarHorizontalAlignment.swift; sourceTree = ""; };
+ E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContainerView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -148,6 +164,7 @@
88916B3C1CF0DF5100469F91 /* Sources */ = {
isa = PBXGroup;
children = (
+ B01E2DCC1F5BA34700E4FA1C /* Assets */,
B096439D1F295DC3004D0129 /* Layout */,
B09643991F295D58004D0129 /* Models */,
B096439A1F295D68004D0129 /* Views */,
@@ -171,14 +188,23 @@
path = Tests;
sourceTree = SOURCE_ROOT;
};
+ B01E2DCC1F5BA34700E4FA1C /* Assets */ = {
+ isa = PBXGroup;
+ children = (
+ B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */,
+ );
+ name = Assets;
+ path = ../Assets;
+ sourceTree = "";
+ };
B09643981F295D43004D0129 /* Extensions */ = {
isa = PBXGroup;
children = (
- 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */,
- 38C867991F50EA1000811974 /* UIView+AutoLayout.swift */,
+ 38C867991F50EA1000811974 /* UIView+Extensions.swift */,
B09643851F286C9E004D0129 /* String+Extensions.swift */,
B096438F1F289142004D0129 /* UIColor+Extensions.swift */,
B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */,
+ B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */,
);
name = Extensions;
sourceTree = "";
@@ -189,9 +215,13 @@
B0655A291F23D77200542A83 /* Sender.swift */,
B0655A2B1F23D81600542A83 /* MessageData.swift */,
37C936971F38F6AC00853DF2 /* Avatar.swift */,
- B0655A271F23D71400542A83 /* MessageDirection.swift */,
- B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */,
+ B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */,
B05530B41F493CFA008BB420 /* DetectorType.swift */,
+ B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */,
+ 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */,
+ B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */,
+ B0147C971F61AF910035B36E /* LabelAlignment.swift */,
+ B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */,
);
name = Models;
sourceTree = "";
@@ -208,6 +238,8 @@
B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */,
B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */,
B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */,
+ E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */,
+ B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */,
);
name = Views;
sourceTree = "";
@@ -218,7 +250,7 @@
B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */,
88916B461CF0DFE600469F91 /* MessageType.swift */,
882D75831DE507320033F95F /* MessagesDataSource.swift */,
- B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */,
+ B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */,
B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */,
B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */,
B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */,
@@ -248,6 +280,7 @@
children = (
88916B3D1CF0DF5100469F91 /* Info.plist */,
88916B3E1CF0DF5100469F91 /* MessageKit.h */,
+ B0147C951F600E290035B36E /* MessageKit+Availability.swift */,
);
name = Supporting;
sourceTree = "";
@@ -345,6 +378,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ B01E2DDB1F5BC10000E4FA1C /* MessageKitAssets.bundle in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -379,36 +413,43 @@
buildActionMask = 2147483647;
files = (
171D5AB91F36712B0053DF69 /* InputTextView.swift in Sources */,
+ B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */,
38C8679E1F50EA4A00811974 /* InputBarItem.swift in Sources */,
B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */,
38C8679A1F50EA1000811974 /* NSConstraintLayoutSet.swift in Sources */,
882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */,
888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */,
+ B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */,
+ E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */,
B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */,
B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */,
+ B0E1756F1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift in Sources */,
B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */,
- B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */,
B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */,
B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */,
+ B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */,
376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */,
B0655A2A1F23D77200542A83 /* Sender.swift in Sources */,
+ B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */,
B074EE931F35587100ABB8C8 /* MessageHeaderView.swift in Sources */,
376AD1821F4258D80083072A /* TestMessageModel.swift in Sources */,
B0655A4D1F244C0600542A83 /* MessageCollectionViewCell.swift in Sources */,
+ B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */,
B0655A2E1F23D8BC00542A83 /* MessagesCollectionView.swift in Sources */,
B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */,
B074EE951F35588A00ABB8C8 /* MessageFooterView.swift in Sources */,
B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */,
B0655A381F23EE8B00542A83 /* MessageInputBar.swift in Sources */,
- 38C8679B1F50EA1000811974 /* UIView+AutoLayout.swift in Sources */,
+ 38C8679B1F50EA1000811974 /* UIView+Extensions.swift in Sources */,
B074EEA81F3971A600ABB8C8 /* MessageLabel.swift in Sources */,
372F6AEB1F36C15600B57FBD /* AvatarView.swift in Sources */,
B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */,
+ B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */,
88916B471CF0DFE600469F91 /* MessageType.swift in Sources */,
- B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */,
+ B0AA1F511F42E91A00BAE583 /* AvatarAlignment.swift in Sources */,
B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */,
37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */,
- B096438E1F2890FB004D0129 /* MessagesDisplayDataSource.swift in Sources */,
+ B096438E1F2890FB004D0129 /* MessagesDisplayDelegate.swift in Sources */,
B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Sources/AvatarPosition.swift b/Sources/AvatarAlignment.swift
similarity index 97%
rename from Sources/AvatarPosition.swift
rename to Sources/AvatarAlignment.swift
index d28d7a01..8dba832d 100644
--- a/Sources/AvatarPosition.swift
+++ b/Sources/AvatarAlignment.swift
@@ -24,7 +24,7 @@
import Foundation
-public enum AvatarPosition {
+public enum AvatarAlignment {
case cellTop
case messageTop
diff --git a/Sources/MessageDirection.swift b/Sources/AvatarHorizontalAlignment.swift
similarity index 88%
rename from Sources/MessageDirection.swift
rename to Sources/AvatarHorizontalAlignment.swift
index cb24a46a..62a07525 100644
--- a/Sources/MessageDirection.swift
+++ b/Sources/AvatarHorizontalAlignment.swift
@@ -1,18 +1,18 @@
/*
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
@@ -22,9 +22,11 @@
SOFTWARE.
*/
-enum MessageDirection {
+import Foundation
- case incoming
- case outgoing
+// MARK: - AvatarVerticalAlignment
+internal enum AvatarHorizontalAlignment {
+ case cellLeading
+ case cellTrailing
}
diff --git a/Sources/Bundle+Extensions.swift b/Sources/Bundle+Extensions.swift
new file mode 100644
index 00000000..550d992d
--- /dev/null
+++ b/Sources/Bundle+Extensions.swift
@@ -0,0 +1,35 @@
+/*
+ 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 Foundation
+
+extension Bundle {
+
+ static func messageKitAssetBundle() -> Bundle {
+ let bundle = Bundle(for: MessagesViewController.self)
+ let resourceUrl = bundle.resourceURL!.appendingPathComponent("MessageKitAssets.bundle")
+ return Bundle(url: resourceUrl)!
+ }
+
+}
diff --git a/Sources/InputBarItem.swift b/Sources/InputBarItem.swift
index 9a8168c3..23854aae 100644
--- a/Sources/InputBarItem.swift
+++ b/Sources/InputBarItem.swift
@@ -135,10 +135,12 @@ open class InputBarButtonItem: UIButton {
}
required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
+ super.init(coder: aDecoder)
+ setup()
}
open func setup() {
+
contentVerticalAlignment = .center
contentHorizontalAlignment = .center
imageView?.contentMode = .scaleAspectFit
diff --git a/Sources/InputTextView.swift b/Sources/InputTextView.swift
index e5a790b4..44c5ce4b 100644
--- a/Sources/InputTextView.swift
+++ b/Sources/InputTextView.swift
@@ -37,6 +37,12 @@ open class InputTextView: UITextView {
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
+
+ open override var text: String! {
+ didSet {
+ placeholderLabel.isHidden = !text.isEmpty
+ }
+ }
private var placeholderLabelConstraintSet: NSLayoutConstraintSet?
@@ -84,7 +90,8 @@ open class InputTextView: UITextView {
}
required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
+ super.init(coder: aDecoder)
+ setup()
}
open func setup() {
diff --git a/Sources/LabelAlignment.swift b/Sources/LabelAlignment.swift
new file mode 100644
index 00000000..bf749a52
--- /dev/null
+++ b/Sources/LabelAlignment.swift
@@ -0,0 +1,35 @@
+/*
+ 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 Foundation
+
+public enum LabelAlignment {
+
+ case cellTrailing
+ case cellLeading
+ case cellCenter
+ case messageTrailing
+ case messageLeading
+
+}
diff --git a/Sources/MessageCellDelegate.swift b/Sources/MessageCellDelegate.swift
index b09ee895..262e9e70 100644
--- a/Sources/MessageCellDelegate.swift
+++ b/Sources/MessageCellDelegate.swift
@@ -24,7 +24,7 @@
import Foundation
-public protocol MessageCellDelegate: class {
+public protocol MessageCellDelegate: class, MessageLabelDelegate {
func didTapMessage(in cell: MessageCollectionViewCell)
@@ -36,7 +36,7 @@ public protocol MessageCellDelegate: class {
}
-extension MessageCellDelegate {
+public extension MessageCellDelegate {
func didTapMessage(in cell: MessageCollectionViewCell) {}
diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift
index dc6f10ae..a596d216 100644
--- a/Sources/MessageCollectionViewCell.swift
+++ b/Sources/MessageCollectionViewCell.swift
@@ -28,12 +28,7 @@ open class MessageCollectionViewCell: UICollectionViewCell {
// MARK: - Properties
- open var messageContainerView: UIView = {
- let messageContainerView = UIView()
- messageContainerView.layer.cornerRadius = 12.0
- messageContainerView.layer.masksToBounds = true
- return messageContainerView
- }()
+ open var messageContainerView: MessageContainerView = MessageContainerView()
open var avatarView: AvatarView = AvatarView()
@@ -51,7 +46,11 @@ open class MessageCollectionViewCell: UICollectionViewCell {
return bottomLabel
}()
- open weak var delegate: MessageCellDelegate?
+ open weak var delegate: MessageCellDelegate? {
+ didSet {
+ messageLabel.delegate = delegate
+ }
+ }
// MARK: - Initializer
@@ -95,9 +94,15 @@ open class MessageCollectionViewCell: UICollectionViewCell {
cellBottomLabel.frame = attributes.cellBottomLabelFrame
cellBottomLabel.textInsets = attributes.cellBottomLabelInsets
- cellTopLabel.textAlignment = attributes.direction == .incoming ? .left : .right
- cellBottomLabel.textAlignment = attributes.direction == .incoming ? .right : .left
+ }
+ override open func prepareForReuse() {
+ messageLabel.text = nil
+ messageLabel.attributedText = nil
+ cellTopLabel.text = nil
+ cellTopLabel.attributedText = nil
+ cellBottomLabel.text = nil
+ cellBottomLabel.attributedText = nil
}
public func configure(with message: MessageType) {
@@ -148,4 +153,5 @@ open class MessageCollectionViewCell: UICollectionViewCell {
func didTapBottomLabel() {
delegate?.didTapBottomLabel(in: self)
}
+
}
diff --git a/Sources/MessageContainerView.swift b/Sources/MessageContainerView.swift
new file mode 100644
index 00000000..7a7baaa8
--- /dev/null
+++ b/Sources/MessageContainerView.swift
@@ -0,0 +1,80 @@
+/*
+ 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
+
+open class MessageContainerView: UIView {
+
+ // MARK: - Properties
+
+ open let imageView = UIImageView()
+
+ open var style: MessageStyle = .none {
+ didSet {
+ imageView.image = style.image
+ updateMessageColor()
+ }
+ }
+
+ open var messageColor: UIColor = .white {
+ didSet {
+ updateMessageColor()
+ }
+ }
+
+ // MARK: - Initializers
+
+ override public init(frame: CGRect) {
+ super.init(frame: frame)
+ setupSubviews()
+ setupConstraints()
+ }
+
+ public required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: - Methods
+
+ func setupSubviews() {
+ imageView.isUserInteractionEnabled = true
+ addSubview(imageView)
+ }
+
+ func setupConstraints() {
+ imageView.fillSuperview()
+ }
+
+ private func updateMessageColor() {
+ switch style {
+ case .none:
+ backgroundColor = messageColor
+ imageView.tintColor = messageColor
+ default:
+ backgroundColor = superview?.backgroundColor
+ imageView.tintColor = messageColor
+ }
+ }
+
+}
diff --git a/Sources/MessageDateHeaderView.swift b/Sources/MessageDateHeaderView.swift
new file mode 100644
index 00000000..f0632bc8
--- /dev/null
+++ b/Sources/MessageDateHeaderView.swift
@@ -0,0 +1,48 @@
+/*
+ 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
+
+open class MessageDateHeaderView: MessageHeaderView {
+
+ // MARK: - Properties
+
+ open let dateLabel = UILabel()
+
+ // MARK: - Initializers
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ addSubview(dateLabel)
+ dateLabel.fillSuperview()
+ dateLabel.textAlignment = .center
+ dateLabel.font = .boldSystemFont(ofSize: 10)
+ dateLabel.textColor = .darkGray
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Sources/MessageFooterView.swift b/Sources/MessageFooterView.swift
index cff8fe1c..57a26a11 100644
--- a/Sources/MessageFooterView.swift
+++ b/Sources/MessageFooterView.swift
@@ -26,7 +26,9 @@ import UIKit
open class MessageFooterView: UICollectionReusableView {
- override init(frame: CGRect) {
+ // MARK: - Initializers
+
+ public override init(frame: CGRect) {
super.init(frame: frame)
}
diff --git a/Sources/MessageHeaderView.swift b/Sources/MessageHeaderView.swift
index c2070517..0a37c18e 100644
--- a/Sources/MessageHeaderView.swift
+++ b/Sources/MessageHeaderView.swift
@@ -26,6 +26,10 @@ import UIKit
open class MessageHeaderView: UICollectionReusableView {
+ // MARK: - Properties
+
+ static let identifier = "MessageHeaderView"
+
override init(frame: CGRect) {
super.init(frame: frame)
}
diff --git a/Sources/MessageInputBar.swift b/Sources/MessageInputBar.swift
index 2a820596..b7fa3ac1 100644
--- a/Sources/MessageInputBar.swift
+++ b/Sources/MessageInputBar.swift
@@ -206,7 +206,8 @@ open class MessageInputBar: UIView {
}
required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
+ super.init(coder: aDecoder)
+ setup()
}
deinit {
@@ -218,7 +219,7 @@ open class MessageInputBar: UIView {
open func setup() {
backgroundColor = .inputBarGray
- autoresizingMask = [.flexibleHeight, .flexibleBottomMargin]
+ autoresizingMask = [.flexibleHeight]
setupSubviews()
setupConstraints()
setupObservers()
@@ -448,5 +449,11 @@ open class MessageInputBar: UIView {
delegate?.messageInputBar(self, didPressSendButtonWith: inputTextView.text)
textViewDidChange()
}
+}
+extension MessageInputBar {
+
+ func createCopy() -> MessageInputBar? {
+ return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as? MessageInputBar
+ }
}
diff --git a/Sources/MessageKit+Availability.swift b/Sources/MessageKit+Availability.swift
new file mode 100644
index 00000000..a3d6e73a
--- /dev/null
+++ b/Sources/MessageKit+Availability.swift
@@ -0,0 +1,114 @@
+/*
+ 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 Foundation
+
+// MARK: - MessagesDisplayDataSource
+
+@available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use MessagesDisplayDelegate.")
+public protocol MessagesDisplayDataSource {}
+
+// MARK: - MessagesCollectionView
+
+public extension MessagesCollectionView {
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use messageCellDelegate.")
+ public weak var messageLabelDelegate: MessageLabelDelegate? {
+ return nil
+ }
+
+}
+
+// MARK: - MessagesCollectionViewFlowLayout
+
+public extension MessagesCollectionViewFlowLayout {
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarSize(for:indexPath:messagesCollectionView)")
+ public var incomingAvatarSize: CGSize {
+ return .zero
+ }
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarSize(for:indexPath:messagesCollectionView)")
+ public var outgoingAvatarSize: CGSize {
+ return .zero
+ }
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use `cellTopLabelPosition(for:indexPath:messagesCollectionView)")
+ public var topLabelExtendsPastAvatar: Bool {
+ return false
+ }
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use `cellBottomLabelPosition(for:indexPath:messagesCollectionView)")
+ public var bottomLabelExtendsPastAvatar: Bool {
+ return false
+ }
+
+}
+
+// MARK: - CellLabelPosition
+
+@available(*, deprecated: 0.7.0, message: "Renamed to LabelAlignment in MessageKit 0.7.0")
+public enum CellLabelPosition {
+
+ case cellTrailing
+ case cellLeading
+ case cellCenter
+ case messageTrailing
+ case messageLeading
+
+}
+
+// MARK: - Avatar Position
+
+@available(*, deprecated: 0.7.0, message: "Renamed to AvatarAlignment in MessageKit 0.7.0")
+public enum AvatarPosition {
+
+ case cellTop
+ case messageTop
+ case messageCenter
+ case messageBottom
+ case cellBottom
+
+}
+
+// MARK: - MessagesLayoutDelegate
+
+public extension MessagesLayoutDelegate {
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarAlignment(for:indexPath:messagesCollectionView)")
+ func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
+ return .cellBottom
+ }
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use cellTopLabelAlignment(for:indexPath:messagesCollectionView)")
+ func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition {
+ return .cellLeading
+ }
+
+ @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use cellBottomLabelAlignment(for:indexPath:messagesCollectionView)")
+ func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition {
+ return .cellLeading
+ }
+
+}
diff --git a/Sources/MessageKitDateFormatter.swift b/Sources/MessageKitDateFormatter.swift
new file mode 100644
index 00000000..e111252e
--- /dev/null
+++ b/Sources/MessageKitDateFormatter.swift
@@ -0,0 +1,66 @@
+/*
+ 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 Foundation
+
+open class MessageKitDateFormatter {
+
+ // MARK: - Properties
+
+ static let shared = MessageKitDateFormatter()
+
+ private let formatter = DateFormatter()
+
+ // MARK: - Initializer
+
+ private init() {}
+
+ // MARK: - Methods
+
+ public func string(from date: Date) -> String {
+ configureDateFormatter(for: date)
+ return formatter.string(from: date)
+ }
+
+ public func attributedString(from date: Date, with attributes: [String: Any]) -> NSAttributedString {
+ let dateString = string(from: date)
+ return NSAttributedString(string: dateString, attributes: attributes)
+ }
+
+ open func configureDateFormatter(for date: Date) {
+ switch true {
+ case Calendar.current.isDateInToday(date) || Calendar.current.isDateInYesterday(date):
+ formatter.doesRelativeDateFormatting = true
+ formatter.dateStyle = .short
+ formatter.timeStyle = .short
+ case Calendar.current.isDate(date, equalTo: Date(), toGranularity: .weekOfYear):
+ formatter.dateFormat = "EEEE h:mm a"
+ case Calendar.current.isDate(date, equalTo: Date(), toGranularity: .year):
+ formatter.dateFormat = "E, d MMM, h:mm a"
+ default:
+ formatter.dateFormat = "MMM d, yyyy, h:mm a"
+ }
+ }
+
+}
diff --git a/Sources/MessageLabelDelegate.swift b/Sources/MessageLabelDelegate.swift
index 02957158..9640032f 100644
--- a/Sources/MessageLabelDelegate.swift
+++ b/Sources/MessageLabelDelegate.swift
@@ -36,7 +36,7 @@ public protocol MessageLabelDelegate: class {
}
-extension MessageLabelDelegate {
+public extension MessageLabelDelegate {
func didSelectAddress(_ addressComponents: [String: String]) {}
diff --git a/Sources/MessageStyle.swift b/Sources/MessageStyle.swift
new file mode 100644
index 00000000..64f6fdc4
--- /dev/null
+++ b/Sources/MessageStyle.swift
@@ -0,0 +1,123 @@
+/*
+ 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
+
+public enum MessageStyle {
+
+ // MARK: - TailCorner
+
+ public enum TailCorner {
+
+ case topLeft
+ case bottomLeft
+ case topRight
+ case bottomRight
+
+ var imageOrientation: UIImageOrientation {
+ switch self {
+ case .bottomRight: return .up
+ case .bottomLeft: return .upMirrored
+ case .topLeft: return .down
+ case .topRight: return .downMirrored
+ }
+ }
+ }
+
+ // MARK: - TailStyle
+
+ public enum TailStyle {
+
+ case curved
+ case pointedEdge
+
+ var imageNameSuffix: String {
+ switch self {
+ case .curved:
+ return "_tail_v2"
+ case .pointedEdge:
+ return "_tail_v1"
+ }
+ }
+ }
+
+ // MARK: - MessageStyle
+
+ case none
+ case bubble
+ case bubbleOutline
+ case bubbleTail(TailCorner, TailStyle)
+ case bubbleTailOutline(TailCorner, TailStyle)
+
+ // MARK: - Public
+
+ public var image: UIImage? {
+
+ guard let path = imagePath else { return nil }
+
+ guard var image = UIImage(contentsOfFile: path) else { return nil }
+
+ switch self {
+ case .none:
+ return nil
+ case .bubble, .bubbleOutline:
+ break
+ case .bubbleTail(let corner, _), .bubbleTailOutline(let corner, _):
+ guard let cgImage = image.cgImage else { return nil }
+ image = UIImage(cgImage: cgImage, scale: image.scale, orientation: corner.imageOrientation)
+ }
+
+ return stretch(image).withRenderingMode(.alwaysTemplate)
+ }
+
+ // MARK: - Private
+
+ private var imageName: String? {
+ switch self {
+ case .bubble:
+ return "bubble_full"
+ case .bubbleOutline:
+ return "bubble_outlined"
+ case .bubbleTail(_, let tailStyle):
+ return "bubble_full" + tailStyle.imageNameSuffix
+ case .bubbleTailOutline(_, let tailStyle):
+ return "bubble_outlined" + tailStyle.imageNameSuffix
+ case .none:
+ return nil
+ }
+ }
+
+ private var imagePath: String? {
+ guard let imageName = imageName else { return nil }
+ let assetBundle = Bundle.messageKitAssetBundle()
+ return assetBundle.path(forResource: imageName, ofType: "png", inDirectory: "Images")
+ }
+
+ private func stretch(_ image: UIImage) -> UIImage {
+ let center = CGPoint(x: image.size.width / 2, y: image.size.height / 2)
+ let capInsets = UIEdgeInsets(top: center.y, left: center.x, bottom: center.y, right: center.x)
+ return image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch)
+ }
+
+}
diff --git a/Sources/MessagesCollectionView.swift b/Sources/MessagesCollectionView.swift
index e5b8ac1f..5236b417 100644
--- a/Sources/MessagesCollectionView.swift
+++ b/Sources/MessagesCollectionView.swift
@@ -30,11 +30,13 @@ open class MessagesCollectionView: UICollectionView {
open weak var messagesDataSource: MessagesDataSource?
+ open weak var messagesDisplayDelegate: MessagesDisplayDelegate?
+
open weak var messagesLayoutDelegate: MessagesLayoutDelegate?
open weak var messageCellDelegate: MessageCellDelegate?
- open weak var messageLabelDelegate: MessageLabelDelegate?
+ open var showsDateHeaderAfterTimeInterval: TimeInterval = 3600
private var indexPathForLastItem: IndexPath? {
@@ -62,12 +64,12 @@ open class MessagesCollectionView: UICollectionView {
scrollToItem(at: indexPath, at: .bottom, animated: animated)
}
- open func dequeueMessageHeaderView(withReuseIdentifier identifier: String = "MessageHeader", for indexPath: IndexPath) -> MessageHeaderView {
+ open func dequeueMessageHeaderView(withReuseIdentifier identifier: String = "MessageHeaderView", for indexPath: IndexPath) -> MessageHeaderView {
let header = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: identifier, for: indexPath)
return header as? MessageHeaderView ?? MessageHeaderView()
}
- open func dequeueMessageFooterView(withReuseIdentifier identifier: String = "MessageFooter", for indexPath: IndexPath) -> MessageFooterView {
+ open func dequeueMessageFooterView(withReuseIdentifier identifier: String = "MessageFooterView", for indexPath: IndexPath) -> MessageFooterView {
let footer = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: identifier, for: indexPath)
return footer as? MessageFooterView ?? MessageFooterView()
}
diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift
index 29328470..3a23b8f1 100644
--- a/Sources/MessagesCollectionViewFlowLayout.swift
+++ b/Sources/MessagesCollectionViewFlowLayout.swift
@@ -33,13 +33,23 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
open var messageToViewEdgePadding: CGFloat
open var cellTopLabelInsets: UIEdgeInsets
- open var topLabelExtendsPastAvatar: Bool
-
open var cellBottomLabelInsets: UIEdgeInsets
- open var bottomLabelExtendsPastAvatar: Bool
- open var incomingAvatarSize: CGSize
- open var outgoingAvatarSize: CGSize
+ open var avatarAlwaysLeading: Bool {
+ willSet {
+ if newValue {
+ avatarAlwaysTrailing = false
+ }
+ }
+ }
+
+ open var avatarAlwaysTrailing: Bool {
+ willSet {
+ if newValue {
+ avatarAlwaysLeading = false
+ }
+ }
+ }
fileprivate var avatarMessagePadding: CGFloat = 4
@@ -65,13 +75,10 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
messageToViewEdgePadding = 30.0
cellTopLabelInsets = .zero
- topLabelExtendsPastAvatar = false
-
cellBottomLabelInsets = .zero
- bottomLabelExtendsPastAvatar = false
- incomingAvatarSize = CGSize(width: 30, height: 30)
- outgoingAvatarSize = CGSize(width: 30, height: 30)
+ avatarAlwaysLeading = false
+ avatarAlwaysTrailing = false
super.init()
@@ -117,29 +124,28 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
guard let dataSource = messagesCollectionView.messagesDataSource else { return }
let indexPath = attributes.indexPath
-
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
// 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))
+ attributes.avatarFrame = CGRect(origin: .zero, size: avatarSize(for: message, at: indexPath))
attributes.messageLabelFont = messageLabelFont
attributes.messageLabelInsets = messageLabelInsets
attributes.cellTopLabelInsets = cellTopLabelInsets
attributes.cellBottomLabelInsets = cellBottomLabelInsets
- attributes.direction = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming
+ attributes.avatarHorizontalAlignment = avatarHorizontalAlignment(for: message)
- let displayDataSource = dataSource as? MessagesDisplayDataSource
- let avatarPosition = displayDataSource?.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom
+ 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: attributes)
- attributes.cellBottomLabelFrame.origin = cellBottomLabelOrigin(for: attributes)
- attributes.avatarFrame.origin = avatarOrigin(for: attributes, with: avatarPosition)
+ attributes.cellTopLabelFrame.origin = cellTopLabelOrigin(for: message, and: attributes)
+ attributes.cellBottomLabelFrame.origin = cellBottomLabelOrigin(for: message, and: attributes)
+ attributes.avatarFrame.origin = avatarOrigin(for: attributes, with: avatarAlignment)
}
@@ -164,27 +170,39 @@ extension MessagesCollectionViewFlowLayout {
// MARK: - Avatar Calculations
- fileprivate func avatarSize(for message: MessageType) -> CGSize {
+ /// The horizontal alignment for the message: .cellTrailing or .cellLeading
+ fileprivate func avatarHorizontalAlignment(for message: MessageType) -> AvatarHorizontalAlignment {
- guard let messagesCollectionView = messagesCollectionView else { return .zero }
- guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .zero }
+ if avatarAlwaysTrailing { return .cellTrailing }
+ if avatarAlwaysLeading { return .cellLeading }
+
+ guard let messagesCollectionView = messagesCollectionView else { return .cellLeading }
+ guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .cellLeading }
- let isOutgoingMessage = messagesDataSource.isFromCurrentSender(message: message)
-
- return isOutgoingMessage ? outgoingAvatarSize : incomingAvatarSize
+ return messagesDataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading
}
- fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarPosition: AvatarPosition) -> CGPoint {
+ /// 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.direction {
- case .incoming:
+ switch attributes.avatarHorizontalAlignment {
+ case .cellLeading:
origin.x = 0
- case .outgoing:
+ case .cellTrailing:
origin.x = contentWidth - avatarWidth
}
@@ -194,12 +212,11 @@ extension MessagesCollectionViewFlowLayout {
let cellBottomLabelHeight = attributes.cellBottomLabelFrame.height
let messageContainerHeight = attributes.messageContainerFrame.height
- switch avatarPosition {
+ switch avatarAlignment {
case .cellTop:
- origin.y = topLabelExtendsPastAvatar ? cellTopLabelHeight : 0
+ origin.y = 0
case .cellBottom:
origin.y = contentHeight - avatarHeight
- if bottomLabelExtendsPastAvatar { origin.y -= cellBottomLabelHeight }
case .messageTop:
origin.y = cellTopLabelHeight
case .messageBottom:
@@ -209,91 +226,98 @@ extension MessagesCollectionViewFlowLayout {
let avatarMidY = avatarHeight / 2
origin.y = contentHeight - cellTopLabelHeight - messageMidY - avatarMidY
}
-
+
return origin
+
+ }
+ // MARK: - Label Size Calculations
+
+ /// The size required fit a NSAttributedString considering a constrained max width.
+ fileprivate func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize {
+
+ let estimatedHeight = attributedText.height(considering: maxWidth)
+ let estimatedWidth = attributedText.width(considering: estimatedHeight)
+
+ let finalHeight = estimatedHeight.rounded(.up)
+ let finalWidth = estimatedWidth > maxWidth ? maxWidth : estimatedWidth.rounded(.up)
+
+ return CGSize(width: finalWidth, height: finalHeight)
+ }
+
+ /// The size required to fit a String considering a constrained max width.
+ fileprivate func labelSize(for text: String, considering maxWidth: CGFloat, and font: UIFont) -> CGSize {
+
+ let estimatedHeight = text.height(considering: maxWidth, and: font)
+ let estimatedWidth = text.width(considering: estimatedHeight, and: font)
+
+ let finalHeight = estimatedHeight.rounded(.up)
+ let finalWidth = estimatedWidth > maxWidth ? maxWidth : estimatedWidth.rounded(.up)
+
+ return CGSize(width: finalWidth, height: finalHeight)
}
// MARK: - Message Container Calculations
- private func availableWidthForMessageContainer(considering message: MessageType) -> CGFloat {
+ /// The total value of the horizontal insets for the message label.
+ private var messageHorizontalInsets: CGFloat {
+ return messageLabelInsets.left + messageLabelInsets.right
+ }
- let avatarWidth = avatarSize(for: message).width
- let avatarWidthPlusPadding = avatarWidth == 0 ? 0 : avatarWidth + avatarMessagePadding
+ /// The total value of the vertical insets for the message label.
+ private var messageVerticalInsets: CGFloat {
+ return messageLabelInsets.top + messageLabelInsets.bottom
+ }
- let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right
- let availableWidth = itemWidth - avatarWidthPlusPadding - horizontalMessageInsets - messageToViewEdgePadding
+ /// The max width available for the message container.
+ private func messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
+
+ var avatarWidth = avatarSize(for: message, at: indexPath).width
+
+ // Only apply padding if avatar width is greater than zero
+ if avatarWidth > 0 { avatarWidth += avatarMessagePadding }
+
+ let availableWidth = itemWidth - avatarWidth - messageToViewEdgePadding - messageHorizontalInsets
return availableWidth
-
- }
-
- private func messageContainerWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
-
- let containerHeight = messageContainerHeight(for: message, at: indexPath)
- let availableWidth = availableWidthForMessageContainer(considering: message)
- let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right
- var estimatedWidth: CGFloat = 0
-
- switch message.data {
- case .text(let text):
- estimatedWidth = text.width(considering: containerHeight, and: messageLabelFont).rounded(.up)
- case .attributedText(let text):
- estimatedWidth = text.width(considering: containerHeight).rounded(.up)
- }
-
- let widthToUse = estimatedWidth > availableWidth ? availableWidth : estimatedWidth
-
- let finalWidth = widthToUse + horizontalMessageInsets
-
- return finalWidth
-
- }
-
- private func messageContainerHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
-
- let availableWidth = availableWidthForMessageContainer(considering: message)
- let verticalMessageInsets = messageLabelInsets.top + messageLabelInsets.bottom
- var estimatedHeight: CGFloat = 0
-
- switch message.data {
- case .text(let text):
- estimatedHeight = text.height(considering: availableWidth, and: messageLabelFont)
- case .attributedText(let text):
- estimatedHeight = text.height(considering: availableWidth)
- }
-
- let finalHeight = estimatedHeight.rounded(.up) + verticalMessageInsets
-
- return finalHeight
-
}
+ /// The size for the message container.
fileprivate func messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
- let messageWidth = messageContainerWidth(for: message, at: indexPath)
- let messageHeight = messageContainerHeight(for: message, at: indexPath)
+ let maxWidth = messageContainerMaxWidth(for: message, at: indexPath)
- return CGSize(width: messageWidth, height: messageHeight)
+ var messageContainerSize: CGSize = .zero
+ switch message.data {
+ case .text(let text):
+ messageContainerSize = labelSize(for: text, considering: maxWidth, and: messageLabelFont)
+ case .attributedText(let text):
+ messageContainerSize = labelSize(for: text, considering: maxWidth)
+ }
+
+ messageContainerSize.width += messageHorizontalInsets
+ messageContainerSize.height += messageVerticalInsets
+
+ return messageContainerSize
}
+ /// The origin for the message container.
fileprivate func messageContainerOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
- let contentWidth = attributes.frame.width
- let avatarWidth = attributes.avatarFrame.width
- let messageContainerWidth = attributes.messageContainerFrame.width
- let cellTopLabelHeight = attributes.cellTopLabelFrame.height
+ var avatarWidth = attributes.avatarFrame.width
+
+ if avatarWidth > 0 { avatarWidth += avatarMessagePadding }
var origin = CGPoint.zero
- switch attributes.direction {
- case .incoming:
- origin.x = avatarWidth + avatarMessagePadding
- origin.y = cellTopLabelHeight
- case .outgoing:
- origin.x = contentWidth - avatarWidth - avatarMessagePadding - messageContainerWidth
- origin.y = cellTopLabelHeight
+ switch attributes.avatarHorizontalAlignment {
+ case .cellLeading:
+ origin.x = avatarWidth
+ origin.y = attributes.cellTopLabelFrame.height
+ case .cellTrailing:
+ origin.x = attributes.frame.width - avatarWidth - attributes.messageContainerFrame.width
+ origin.y = attributes.cellTopLabelFrame.height
}
return origin
@@ -302,44 +326,99 @@ extension MessagesCollectionViewFlowLayout {
// MARK: - Cell Top Label Calculations
- private func cellTopLabelWidth(for message: MessageType) -> CGFloat {
- if topLabelExtendsPastAvatar {
- return itemWidth
- } else {
- return itemWidth - avatarSize(for: message).width - avatarMessagePadding - messageToViewEdgePadding
+ /// The total value of the horizontal insets for the cell top label.
+ private var topLabelHorizontalInsets: CGFloat {
+ return cellTopLabelInsets.left + cellTopLabelInsets.right
+ }
+
+ /// The total value of the vertical insets for the cell top label.
+ private var topLabelVerticalInsets: CGFloat {
+ return cellTopLabelInsets.top + cellTopLabelInsets.bottom
+ }
+
+ /// 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)
+
+ var avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width
+
+ // Only apply padding is avatar width is greater than zero
+ if avatarWidth > 0 { avatarWidth += avatarMessagePadding }
+
+ 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
}
}
- private func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
-
- guard let messagesCollectionView = messagesCollectionView else { return 0 }
- guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return 0 }
- guard let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 }
-
- let availableWidth = cellTopLabelWidth(for: message) - cellTopLabelInsets.left - cellTopLabelInsets.right
-
- let estimatedHeight = topLabelText.height(considering: availableWidth)
-
- return estimatedHeight.rounded(.up)
-
- }
-
+ /// The size of the cell top label.
fileprivate func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
- let topLabelWidth = cellTopLabelWidth(for: message)
- let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
+ 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 }
+
+ let maxWidth = topLabelMaxWidth(for: message, at: indexPath)
+ var size = labelSize(for: topLabelText, considering: maxWidth)
+
+ size.width += topLabelHorizontalInsets
+ size.height += topLabelVerticalInsets
+
+ return size
- return CGSize(width: topLabelWidth, height: topLabelHeight)
-
}
- fileprivate func cellTopLabelOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
+ /// 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
- if topLabelExtendsPastAvatar == false {
- let avatarWidth = attributes.avatarFrame.width
- origin.x = avatarWidth + avatarMessagePadding
+ 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 + avatarMessagePadding
+ case (.messageLeading, .cellTrailing):
+ origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width
+ case (.messageTrailing, .cellTrailing):
+ origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellTopLabelFrame.width
+ case (.messageTrailing, .cellLeading):
+ origin.x = attributes.frame.width - messageToViewEdgePadding - attributes.cellTopLabelFrame.width
}
return origin
@@ -348,49 +427,100 @@ extension MessagesCollectionViewFlowLayout {
// MARK: - Cell Bottom Label Calculations
- private func cellBottomLabelWidth(for message: MessageType) -> CGFloat {
- if bottomLabelExtendsPastAvatar {
- return itemWidth
- } else {
- return itemWidth - avatarSize(for: message).width - avatarMessagePadding - messageToViewEdgePadding
+ /// The total value of the horizontal insets for the cell bottom label
+ private var bottomLabelHorizontalInsets: CGFloat {
+ return cellBottomLabelInsets.left + cellBottomLabelInsets.right
+ }
+
+ /// The total value of the vertical insets for the cell bottom label.
+ private var bottomLabelVerticalInsets: CGFloat {
+ return cellBottomLabelInsets.top + cellBottomLabelInsets.bottom
+ }
+
+ /// 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)
+
+ var avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width
+
+ // Only apply padding is avatar width is greater than zero
+ if avatarWidth > 0 { avatarWidth += avatarMessagePadding }
+
+ switch (labelHorizontal, avatarHorizontal, avatarVertical) {
+
+ case (.cellLeading, .cellTrailing, .cellBottom), (.cellTrailing, .cellLeading, .cellBottom):
+ return itemWidth - avatarWidth - bottomLabelHorizontalInsets
+
+ case (.cellLeading, _, _), (.cellTrailing, _, _):
+ return itemWidth - bottomLabelHorizontalInsets
+
+ case (.cellCenter, .cellLeading, .cellBottom), (.cellCenter, .cellTrailing, .cellBottom):
+ return itemWidth - (avatarWidth * 2) - bottomLabelHorizontalInsets
+
+ case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _):
+ return itemWidth - bottomLabelHorizontalInsets
+
+ case (.messageTrailing, .cellLeading, .cellBottom), (.messageLeading, .cellTrailing, .cellBottom):
+ return messageContainerSize(for: message, at: indexPath).width - bottomLabelHorizontalInsets
+
+ case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _):
+ return itemWidth - avatarWidth - bottomLabelHorizontalInsets
+
+ case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _):
+ return messageContainerSize(for: message, at: indexPath).width + avatarWidth - bottomLabelHorizontalInsets
}
}
- private func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
-
- guard let messagesCollectionView = messagesCollectionView else { return 0 }
- guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return 0 }
- guard let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 }
-
- let availableWidth = cellBottomLabelWidth(for: message) - cellBottomLabelInsets.left - cellBottomLabelInsets.right
-
- let estimatedHeight = bottomLabelText.height(considering: availableWidth)
-
- return estimatedHeight.rounded(.up)
- }
-
+ /// The size for the cell bottom label.
fileprivate func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
- let bottomLabelWidth = cellBottomLabelWidth(for: message)
- let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath)
+ 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 }
- return CGSize(width: bottomLabelWidth, height: bottomLabelHeight)
+ let maxWidth = bottomLabelMaxWidth(for: message, at: indexPath)
+ var size = labelSize(for: bottomLabelText, considering: maxWidth)
+
+ size.width += bottomLabelHorizontalInsets
+ size.height += bottomLabelVerticalInsets
+
+ return size
}
- fileprivate func cellBottomLabelOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
+ /// The origin for the cell bottom label.
+ fileprivate func cellBottomLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {
- var origin = CGPoint.zero
+ guard let messagesCollectionView = messagesCollectionView else { return .zero }
+ guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
- let contentHeight = attributes.frame.height
- let cellBottomLabelHeight = attributes.cellBottomLabelFrame.height
+ let labelAlignment = layoutDelegate.cellBottomLabelAlignment(for: message, at: attributes.indexPath, in: messagesCollectionView)
+ let avatarAlignment = avatarHorizontalAlignment(for: message)
- origin.y = contentHeight - cellBottomLabelHeight
+ var origin = CGPoint(x: 0, y: attributes.frame.height - attributes.cellBottomLabelFrame.height)
- if bottomLabelExtendsPastAvatar == false {
- let avatarWidth = attributes.avatarFrame.width
- origin.x = avatarWidth + avatarMessagePadding
+ switch (labelAlignment, avatarAlignment) {
+ 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 + avatarMessagePadding
+ case (.messageLeading, .cellTrailing):
+ origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width
+ case (.messageTrailing, .cellTrailing):
+ origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellBottomLabelFrame.width
+ case (.messageTrailing, .cellLeading):
+ origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellBottomLabelFrame.width
}
return origin
@@ -399,29 +529,41 @@ extension MessagesCollectionViewFlowLayout {
// MARK: - Cell Size Calculations
+ /// The minimum height for the cell.
private func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
- let size = avatarSize(for: message)
- let avatarHeight = size.height
- let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath)
- let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
+ guard let messagesCollectionView = messagesCollectionView else { return 0 }
+ guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 }
- let minimumHeight = topLabelHeight + avatarHeight + bottomLabelHeight
+ 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
+
+ switch avatarAlignment {
+ case .cellTop:
+ return max(avatarHeight, topLabelHeight) + bottomLabelHeight
+ case .cellBottom:
+ return max(avatarHeight, bottomLabelHeight) + topLabelHeight
+ case .messageTop, .messageCenter, .messageBottom:
+ return topLabelHeight + avatarHeight + bottomLabelHeight
+ }
- return minimumHeight
-
}
+ /// The calculated height of the cell not considering the minimum height.
private func estimatedCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
- let containerHeight = messageContainerHeight(for: message, at: indexPath)
- let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
- let bottomLabelheight = cellBottomLabelHeight(for: message, at: indexPath)
+ let messageContainerHeight = messageContainerSize(for: message, at: indexPath).height
+ let cellTopLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
+ let cellBottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height
- return topLabelHeight + containerHeight + bottomLabelheight
+ return messageContainerHeight + cellBottomLabelHeight + cellTopLabelHeight
}
+ /// The size for the cell considering all cell contents.
open func sizeForItem(at indexPath: IndexPath) -> CGSize {
guard let messagesCollectionView = messagesCollectionView else { return .zero }
@@ -431,9 +573,10 @@ extension MessagesCollectionViewFlowLayout {
let minimumHeight = minimumCellHeight(for: message, at: indexPath)
let estimatedHeight = estimatedCellHeight(for: message, at: indexPath)
- let finalHeight = estimatedHeight < minimumHeight ? minimumHeight : estimatedHeight
-
- return CGSize(width: itemWidth, height: finalHeight)
+
+ let itemHeight = estimatedHeight > minimumHeight ? estimatedHeight : minimumHeight
+
+ return CGSize(width: itemWidth, height: itemHeight)
}
diff --git a/Sources/MessagesCollectionViewLayoutAttributes.swift b/Sources/MessagesCollectionViewLayoutAttributes.swift
index 89c5dfc8..7bd823ac 100644
--- a/Sources/MessagesCollectionViewLayoutAttributes.swift
+++ b/Sources/MessagesCollectionViewLayoutAttributes.swift
@@ -28,8 +28,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib
// MARK: - Properties
- var direction: MessageDirection = .incoming
-
+ var avatarHorizontalAlignment = AvatarHorizontalAlignment.cellLeading
var avatarFrame: CGRect = .zero
var messageContainerFrame: CGRect = .zero
@@ -47,7 +46,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib
override func copy(with zone: NSZone? = nil) -> Any {
// swiftlint:disable force_cast
let copy = super.copy(with: zone) as! MessagesCollectionViewLayoutAttributes
- copy.direction = direction
+ copy.avatarHorizontalAlignment = avatarHorizontalAlignment
copy.avatarFrame = avatarFrame
copy.messageContainerFrame = messageContainerFrame
copy.messageLabelFont = messageLabelFont
diff --git a/Sources/MessagesDataSource.swift b/Sources/MessagesDataSource.swift
index ac275cd8..4014c20c 100644
--- a/Sources/MessagesDataSource.swift
+++ b/Sources/MessagesDataSource.swift
@@ -34,6 +34,12 @@ public protocol MessagesDataSource: class {
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int
+ func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar
+
+ func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
+
+ func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
+
}
public extension MessagesDataSource {
@@ -42,4 +48,16 @@ public extension MessagesDataSource {
return message.sender == currentSender()
}
+ func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
+ return Avatar()
+ }
+
+ func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+ return nil
+ }
+
+ func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+ return nil
+ }
+
}
diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDelegate.swift
similarity index 55%
rename from Sources/MessagesDisplayDataSource.swift
rename to Sources/MessagesDisplayDelegate.swift
index ab750b09..2cad787e 100644
--- a/Sources/MessagesDisplayDataSource.swift
+++ b/Sources/MessagesDisplayDelegate.swift
@@ -24,58 +24,56 @@
import Foundation
-public protocol MessagesDisplayDataSource: class, MessagesDataSource {
+public protocol MessagesDisplayDelegate: class {
func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
+
+ func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle
func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
- func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar
-
- func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition
-
func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView?
+
+ func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool
func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView?
-
- func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
-
- func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
-
+
}
-public extension MessagesDisplayDataSource {
+public extension MessagesDisplayDelegate {
func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
- return isFromCurrentSender(message: message) ? .white : .black
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return .darkText }
+ return dataSource.isFromCurrentSender(message: message) ? .white : .darkText
}
- func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
- return isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray
- }
-
- func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
- return .cellBottom
+ func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
+ return .bubble
}
- func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
- return Avatar()
+ func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return .white }
+ return dataSource.isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray
}
func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? {
- return nil
+ let header = messagesCollectionView.dequeueMessageHeaderView(withReuseIdentifier: "MessageDateHeaderView", for: indexPath) as? MessageDateHeaderView
+ header?.dateLabel.text = MessageKitDateFormatter.shared.string(from: message.sentDate)
+ return header
}
-
+
+ func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool {
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return false }
+ if indexPath.section == 0 { return false }
+ let previousSection = indexPath.section - 1
+ let previousIndexPath = IndexPath(item: 0, section: previousSection)
+ let previousMessage = dataSource.messageForItem(at: previousIndexPath, in: messagesCollectionView)
+ let timeIntervalSinceLastMessage = message.sentDate.timeIntervalSince(previousMessage.sentDate)
+ return timeIntervalSinceLastMessage >= messagesCollectionView.showsDateHeaderAfterTimeInterval
+ }
+
func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? {
return nil
}
- func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
- return nil
- }
-
- func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
- return nil
- }
-
}
diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift
index b165f19e..b76538b6 100644
--- a/Sources/MessagesLayoutDelegate.swift
+++ b/Sources/MessagesLayoutDelegate.swift
@@ -1,18 +1,18 @@
/*
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
@@ -26,16 +26,44 @@ import Foundation
public protocol MessagesLayoutDelegate: class {
+ func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment
+
+ func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment
+
+ func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment
+
+ func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize
+
func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize
}
-extension MessagesLayoutDelegate {
+public extension MessagesLayoutDelegate {
+
+ func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment {
+ return .cellBottom
+ }
+
+ func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter }
+ return dataSource.isFromCurrentSender(message: message) ? .messageTrailing : .messageLeading
+ }
+
+ func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter }
+ return dataSource.isFromCurrentSender(message: message) ? .messageLeading : .messageTrailing
+ }
+
+ func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
+ return CGSize(width: 30, height: 30)
+ }
func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
- return .zero
+ guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return .zero }
+ let shouldDisplay = displayDelegate.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView)
+ return shouldDisplay ? CGSize(width: messagesCollectionView.bounds.width, height: 12) : .zero
}
func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift
index fdcfcf72..09ddbc42 100644
--- a/Sources/MessagesViewController.swift
+++ b/Sources/MessagesViewController.swift
@@ -30,14 +30,16 @@ open class MessagesViewController: UIViewController {
open var messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout())
- open var messageInputBar = MessageInputBar()
+ open var messageInputBar = MessageInputBar()
+ private var messageInputBarCopy: MessageInputBar?
+
override open var canBecomeFirstResponder: Bool {
return true
}
override open var inputAccessoryView: UIView? {
- return messageInputBar
+ return messageInputBar
}
open override var shouldAutorotate: Bool {
@@ -55,26 +57,24 @@ open class MessagesViewController: UIViewController {
setupSubviews()
setupConstraints()
-
- registerReusableCells()
- registerReusableHeaders()
- registerReusableFooters()
-
+ registerReusableViews()
setupDelegates()
-
- // https://stackoverflow.com/questions/31049651/uitextview-as-inputaccessoryview-doesnt-render-text-until-after-animation
- // Calling this on the root view avoids a side effect where the inputAccessoryView dissapears after tap
- //view.snapshotView(afterScreenUpdates: true)
}
- override open func viewDidLayoutSubviews() {
- messagesCollectionView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: messageInputBar.frame.height, right: 0)
+ open override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ setupMessageInputBarCopy()
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addKeyboardObservers()
+ removeMessageInputBarCopy()
+ }
+
+ override open func viewDidLayoutSubviews() {
+ messagesCollectionView.contentInset.top = topLayoutGuide.length
}
// MARK: - Initializers
@@ -90,20 +90,21 @@ open class MessagesViewController: UIViewController {
messagesCollectionView.dataSource = self
}
- open func registerReusableCells() {
- messagesCollectionView.register(MessageCollectionViewCell.self, forCellWithReuseIdentifier: "MessageCell")
- }
+ private func registerReusableViews() {
+ messagesCollectionView.register(MessageCollectionViewCell.self,
+ forCellWithReuseIdentifier: "MessageCell")
- open func registerReusableFooters() {
messagesCollectionView.register(MessageFooterView.self,
forSupplementaryViewOfKind: UICollectionElementKindSectionFooter,
- withReuseIdentifier: "MessageFooter")
- }
+ withReuseIdentifier: "MessageFooterView")
- open func registerReusableHeaders() {
messagesCollectionView.register(MessageHeaderView.self,
forSupplementaryViewOfKind: UICollectionElementKindSectionHeader,
- withReuseIdentifier: "MessageHeader")
+ withReuseIdentifier: "MessageHeaderView")
+
+ messagesCollectionView.register(MessageDateHeaderView.self,
+ forSupplementaryViewOfKind: UICollectionElementKindSectionHeader,
+ withReuseIdentifier: "MessageDateHeaderView")
}
private func setupSubviews() {
@@ -127,6 +128,20 @@ open class MessagesViewController: UIViewController {
toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: 0))
}
+ // MARK: - MessageInputBar
+ // Fixes bug where MessageInputBar text renders after viewDidAppear
+
+ private func setupMessageInputBarCopy() {
+ messageInputBarCopy = messageInputBar.createCopy()
+ guard let copy = messageInputBarCopy else { return }
+ view.addSubview(copy)
+ copy.addConstraints(nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
+ }
+
+ private func removeMessageInputBarCopy() {
+ messageInputBarCopy?.removeFromSuperview()
+ messageInputBarCopy = nil
+ }
}
// MARK: - UICollectionViewDelegate & UICollectionViewDelegateFlowLayout Conformance
@@ -170,30 +185,29 @@ extension MessagesViewController: UICollectionViewDataSource {
if cell.delegate == nil { cell.delegate = cellDelegate }
}
- if let messageLabelDelegate = messagesCollectionView.messageLabelDelegate {
- if cell.messageLabel.delegate == nil { cell.messageLabel.delegate = messageLabelDelegate }
- }
-
guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return cell }
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+ let avatar = messagesDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView)
+ let topLabelText = messagesDataSource.cellTopLabelAttributedText(for: message, at: indexPath)
+ let bottomLabelText = messagesDataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
- if let displayDataSource = messagesDataSource as? MessagesDisplayDataSource {
+ if let displayDelegate = messagesCollectionView.messagesDisplayDelegate {
- let messageColor = displayDataSource.backgroundColor(for: message, at: indexPath, in: messagesCollectionView)
- let textColor = displayDataSource.textColor(for: message, at: indexPath, in: messagesCollectionView)
- let avatar = displayDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView)
- let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath)
- let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
+ let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView)
+ let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView)
+ let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView)
- cell.avatarView.set(avatar: avatar)
cell.messageLabel.textColor = textColor
- cell.messageContainerView.backgroundColor = messageColor
- cell.cellTopLabel.attributedText = topLabelText
- cell.cellBottomLabel.attributedText = bottomLabelText
+ cell.messageContainerView.messageColor = messageColor
+ cell.messageContainerView.style = messageStyle
}
+ // Must be set after configuring displayDelegate properties
+ cell.avatarView.set(avatar: avatar)
+ cell.cellTopLabel.attributedText = topLabelText
+ cell.cellBottomLabel.attributedText = bottomLabelText
cell.configure(with: message)
return cell
@@ -203,15 +217,16 @@ extension MessagesViewController: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let messagesCollectionView = collectionView as? MessagesCollectionView else { return UICollectionReusableView() }
- guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return UICollectionReusableView() }
+ guard let dataSource = messagesCollectionView.messagesDataSource else { return UICollectionReusableView() }
+ guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return UICollectionReusableView() }
- let message = displayDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+ let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
switch kind {
case UICollectionElementKindSectionHeader:
- return displayDataSource.messageHeaderView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageHeaderView()
+ return displayDelegate.messageHeaderView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageHeaderView()
case UICollectionElementKindSectionFooter:
- return displayDataSource.messageFooterView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageFooterView()
+ return displayDelegate.messageFooterView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageFooterView()
default:
fatalError("Unrecognized element of kind: \(kind)")
}
@@ -245,7 +260,8 @@ extension MessagesViewController: UICollectionViewDataSource {
extension MessagesViewController {
fileprivate func addKeyboardObservers() {
-
+
+ NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillChangeFrame, object: nil)
@@ -256,6 +272,7 @@ extension MessagesViewController {
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillChangeFrame, object: nil)
+ NotificationCenter.default.removeObserver(self, name: .UIKeyboardDidShow, object: nil)
}
func handleKeyboardDidChangeState(_ notification: Notification) {
@@ -264,6 +281,10 @@ extension MessagesViewController {
let keyboardFrame = self.view.convert(keyboardEndFrame, from: self.view.window)
switch notification.name {
+ case Notification.Name.UIKeyboardDidShow:
+ // Only runs once
+ messagesCollectionView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: messageInputBar.frame.height, right: 0)
+ NotificationCenter.default.removeObserver(self, name: .UIKeyboardDidShow, object: nil)
case Notification.Name.UIKeyboardDidChangeFrame, Notification.Name.UIKeyboardWillShow:
if (keyboardFrame.origin.y + keyboardFrame.size.height) > view.frame.size.height {
// Hardware keyboard is found
diff --git a/Sources/UIView+AutoLayout.swift b/Sources/UIView+Extensions.swift
similarity index 100%
rename from Sources/UIView+AutoLayout.swift
rename to Sources/UIView+Extensions.swift
diff --git a/Tests/MessagesDisplayDataSourceTests.swift b/Tests/MessagesDisplayDataSourceTests.swift
index 007114de..0dddd146 100644
--- a/Tests/MessagesDisplayDataSourceTests.swift
+++ b/Tests/MessagesDisplayDataSourceTests.swift
@@ -26,7 +26,7 @@ import XCTest
@testable import MessageKit
-class MessagesDisplayDataSourceTests: XCTestCase {
+class MessagesDisplayDelegateTests: XCTestCase {
var testClass: TestMessagesViewControllerModel!
var window: UIWindow!
@@ -54,7 +54,7 @@ class MessagesDisplayDataSourceTests: XCTestCase {
func testMessageTextColorDefaultState() {
XCTAssertEqual(testClass.textColor(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0), in: testClass.messagesCollectionView), UIColor.white)
- XCTAssertEqual(testClass.textColor(for: testClass.messageList[1], at: IndexPath(item: 1, section: 0), in: testClass.messagesCollectionView), UIColor.black)
+ XCTAssertEqual(testClass.textColor(for: testClass.messageList[1], at: IndexPath(item: 1, section: 0), in: testClass.messagesCollectionView), UIColor.darkText)
}
func testBackGroundColorDefaultState() {
diff --git a/Tests/TestMessagesViewControllerModel.swift b/Tests/TestMessagesViewControllerModel.swift
index db8f0e4e..b87c8067 100644
--- a/Tests/TestMessagesViewControllerModel.swift
+++ b/Tests/TestMessagesViewControllerModel.swift
@@ -24,7 +24,7 @@
import Foundation
-class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDataSource {
+class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDelegate {
var messageList: [TestMessage] = []