Merge remote-tracking branch 'MessageKit/master'

This commit is contained in:
Güngör
2017-09-10 11:31:33 -07:00
53 changed files with 1205 additions and 351 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

+61
View File
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StringsTable</key>
<string>Root</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string>Group</string>
</dict>
<dict>
<key>Type</key>
<string>PSTextFieldSpecifier</string>
<key>Title</key>
<string>Name</string>
<key>Key</key>
<string>name_preference</string>
<key>DefaultValue</key>
<string></string>
<key>IsSecure</key>
<false/>
<key>KeyboardType</key>
<string>Alphabet</string>
<key>AutocapitalizationType</key>
<string>None</string>
<key>AutocorrectionType</key>
<string>No</string>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Enabled</string>
<key>Key</key>
<string>enabled_preference</string>
<key>DefaultValue</key>
<true/>
</dict>
<dict>
<key>Type</key>
<string>PSSliderSpecifier</string>
<key>Key</key>
<string>slider_preference</string>
<key>DefaultValue</key>
<real>0.5</real>
<key>MinimumValue</key>
<integer>0</integer>
<key>MaximumValue</key>
<integer>1</integer>
<key>MinimumValueImage</key>
<string></string>
<key>MaximumValueImage</key>
<string></string>
</dict>
</array>
</dict>
</plist>
Binary file not shown.
+5 -1
View File
@@ -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
@@ -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";
@@ -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)
}
}
+37 -32
View File
@@ -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:
+1 -1
View File
@@ -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.'
+58 -17
View File
@@ -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 = "<group>"; };
37C936971F38F6AC00853DF2 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSConstraintLayoutSet.swift; sourceTree = "<group>"; };
38C867991F50EA1000811974 /* UIView+AutoLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayout.swift"; sourceTree = "<group>"; };
38C867991F50EA1000811974 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
38C8679D1F50EA4A00811974 /* InputBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputBarItem.swift; sourceTree = "<group>"; };
882D75831DE507320033F95F /* MessagesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDataSource.swift; sourceTree = "<group>"; };
888CEBFB1D3FD525005178DE /* MessagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = "<group>"; };
@@ -77,11 +85,17 @@
88916B431CF0DF5900469F91 /* MessageKitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitTests.swift; sourceTree = "<group>"; };
88916B461CF0DFE600469F91 /* MessageType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = "<group>"; };
B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = "<group>"; };
B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = "<group>"; };
B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = "<group>"; };
B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = "<group>"; };
B0147C971F61AF910035B36E /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = "<group>"; };
B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = "<group>"; };
B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = "<group>"; };
B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = "<group>"; };
B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = MessageKitAssets.bundle; sourceTree = "<group>"; };
B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellDelegate.swift; sourceTree = "<group>"; };
B05530B41F493CFA008BB420 /* DetectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = "<group>"; };
B0655A271F23D71400542A83 /* MessageDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDirection.swift; sourceTree = "<group>"; };
B0655A291F23D77200542A83 /* Sender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sender.swift; sourceTree = "<group>"; };
B0655A2B1F23D81600542A83 /* MessageData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageData.swift; sourceTree = "<group>"; };
B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionView.swift; sourceTree = "<group>"; };
@@ -93,10 +107,12 @@
B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesLayoutDelegate.swift; sourceTree = "<group>"; };
B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabel.swift; sourceTree = "<group>"; };
B09643851F286C9E004D0129 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDataSource.swift; sourceTree = "<group>"; };
B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDelegate.swift; sourceTree = "<group>"; };
B096438F1F289142004D0129 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = "<group>"; };
B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarAlignment.swift; sourceTree = "<group>"; };
B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarHorizontalAlignment.swift; sourceTree = "<group>"; };
E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContainerView.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
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 = "<group>";
@@ -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 = "<group>";
@@ -208,6 +238,8 @@
B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */,
B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */,
B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */,
E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */,
B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */,
);
name = Views;
sourceTree = "<group>";
@@ -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 = "<group>";
@@ -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;
@@ -24,7 +24,7 @@
import Foundation
public enum AvatarPosition {
public enum AvatarAlignment {
case cellTop
case messageTop
@@ -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
}
+35
View File
@@ -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)!
}
}
+3 -1
View File
@@ -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
+8 -1
View File
@@ -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() {
+35
View File
@@ -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
}
+2 -2
View File
@@ -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) {}
+15 -9
View File
@@ -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)
}
}
+80
View File
@@ -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
}
}
}
+48
View File
@@ -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")
}
}
+3 -1
View File
@@ -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)
}
+4
View File
@@ -26,6 +26,10 @@ import UIKit
open class MessageHeaderView: UICollectionReusableView {
// MARK: - Properties
static let identifier = "MessageHeaderView"
override init(frame: CGRect) {
super.init(frame: frame)
}
+9 -2
View File
@@ -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
}
}
+114
View File
@@ -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
}
}
+66
View File
@@ -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"
}
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ public protocol MessageLabelDelegate: class {
}
extension MessageLabelDelegate {
public extension MessageLabelDelegate {
func didSelectAddress(_ addressComponents: [String: String]) {}
+123
View File
@@ -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)
}
}
+5 -3
View File
@@ -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()
}
+306 -163
View File
@@ -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)
}
@@ -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
+18
View File
@@ -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
}
}
@@ -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
}
}
+34 -6
View File
@@ -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 {
+61 -40
View File
@@ -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
+2 -2
View File
@@ -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() {
+1 -1
View File
@@ -24,7 +24,7 @@
import Foundation
class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDataSource {
class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDelegate {
var messageList: [TestMessage] = []