Merge remote-tracking branch 'MessageKit/master'
|
After Width: | Height: | Size: 455 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 634 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 732 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 876 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,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.'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)!
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ import UIKit
|
||||
|
||||
open class MessageHeaderView: UICollectionReusableView {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
static let identifier = "MessageHeaderView"
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public protocol MessageLabelDelegate: class {
|
||||
|
||||
}
|
||||
|
||||
extension MessageLabelDelegate {
|
||||
public extension MessageLabelDelegate {
|
||||
|
||||
func didSelectAddress(_ addressComponents: [String: String]) {}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDataSource {
|
||||
class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDelegate {
|
||||
|
||||
var messageList: [TestMessage] = []
|
||||
|
||||
|
||||