mirror of
https://github.com/MessageKit/MessageKit.git
synced 2026-02-06 19:03:19 +00:00
@@ -3,3 +3,5 @@ disabled_rules:
|
||||
- identifier_name
|
||||
- trailing_whitespace
|
||||
- line_length
|
||||
- type_body_length
|
||||
- file_length
|
||||
|
||||
@@ -7,6 +7,14 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa
|
||||
## Upcoming release
|
||||
----------------
|
||||
|
||||
## [Prerelease] 0.5.0
|
||||
|
||||
This release closes the [0.5 milestone](https://github.com/MessageKit/MessageKit/milestone/5?closed=1).
|
||||
|
||||
## [Prerelease] 0.4.0
|
||||
|
||||
This release closes the [0.4 milestone](https://github.com/MessageKit/MessageKit/milestone/4?closed=1).
|
||||
|
||||
## [Prerelease] 0.3.0
|
||||
|
||||
This release closes the [0.3 milestone](https://github.com/MessageKit/MessageKit/milestone/3?closed=1).
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ If you're new to Open Source or Swift, the MessageKit community is a great place
|
||||
- Please check the [README](https://github.com/MessageKit/MessageKit/blob/master/README.md) to see if your question is answered there.
|
||||
- Search [open issues](https://github.com/MessageKit/MessageKit/issues?q=is%3Aopen+is%3Aissue) and [closed issues](https://github.com/MessageKit/MessageKit/issues?q=is%3Aissue+is%3Aclosed) to avoid opening a duplicate issue.
|
||||
- Avoiding duplicate issues organizes all relevant information for project maintainers and other users.
|
||||
- If no issues represent your problem, please open a new issue with a good tile and useful description.
|
||||
- If no issues represent your problem, please open a new issue with a good title and useful description.
|
||||
|
||||
- Information to Provide When Opening an Issue:
|
||||
- MessageKit version(s)
|
||||
|
||||
@@ -34,8 +34,10 @@ class ConversationViewController: MessagesViewController {
|
||||
|
||||
messageList = SampleData().getMessages()
|
||||
messagesCollectionView.messagesDataSource = self
|
||||
messagesCollectionView.messageCellDelegate = self
|
||||
messagesCollectionView.messagesLayoutDelegate = self
|
||||
|
||||
messagesCollectionView.messageCellDelegate = self
|
||||
messagesCollectionView.messageLabelDelegate = self
|
||||
messageInputBar.delegate = self
|
||||
}
|
||||
}
|
||||
@@ -66,6 +68,10 @@ extension ConversationViewController: MessagesDisplayDataSource {
|
||||
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)
|
||||
}
|
||||
@@ -114,6 +120,36 @@ extension ConversationViewController: MessageCellDelegate {
|
||||
print("Message tapped")
|
||||
}
|
||||
|
||||
func didTapTopLabel(in cell: MessageCollectionViewCell) {
|
||||
print("Top label tapped")
|
||||
}
|
||||
|
||||
func didTapBottomLabel(in cell: MessageCollectionViewCell) {
|
||||
print("Bottom label tapped")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MessageLabelDelegate
|
||||
|
||||
extension ConversationViewController: MessageLabelDelegate {
|
||||
|
||||
func didSelectAddress(_ addressComponents: [String : String]) {
|
||||
print("Address Selected: \(addressComponents)")
|
||||
}
|
||||
|
||||
func didSelectDate(_ date: Date) {
|
||||
print("Date Selected: \(date)")
|
||||
}
|
||||
|
||||
func didSelectPhoneNumber(_ phoneNumber: String) {
|
||||
print("Phone Number Selected: \(phoneNumber)")
|
||||
}
|
||||
|
||||
func didSelectURL(_ url: URL) {
|
||||
print("URL Selected: \(url)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MessageInputBarDelegate
|
||||
|
||||
@@ -39,9 +39,11 @@ struct SampleData {
|
||||
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)
|
||||
|
||||
let msg9Text = NSString(string: "Use .attributedText() to add bold, italic, colored text and more...")
|
||||
|
||||
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()"))
|
||||
@@ -59,8 +61,13 @@ struct SampleData {
|
||||
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)
|
||||
|
||||
return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9]
|
||||
return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13]
|
||||
}
|
||||
|
||||
func getCurrentSender() -> Sender {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'MessageKit'
|
||||
s.version = '0.4.0'
|
||||
s.version = '0.5.0'
|
||||
s.license = { :type => "MIT", :file => "LICENSE.md" }
|
||||
|
||||
s.summary = 'An elegant messages UI library for iOS.'
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
171D5AB91F36712B0053DF69 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171D5AB81F36712B0053DF69 /* InputTextView.swift */; };
|
||||
372F6AEB1F36C15600B57FBD /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372F6AEA1F36C15600B57FBD /* AvatarView.swift */; };
|
||||
372F6AEF1F36C61000B57FBD /* AvatarViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */; };
|
||||
376AD1821F4258D80083072A /* TestMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376AD1811F4258D80083072A /* TestMessageModel.swift */; };
|
||||
376AD1861F4259270083072A /* MessagesDisplayDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376AD1851F4259270083072A /* MessagesDisplayDataSourceTests.swift */; };
|
||||
376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376AD1871F4259D20083072A /* TestMessagesViewControllerModel.swift */; };
|
||||
37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C936971F38F6AC00853DF2 /* Avatar.swift */; };
|
||||
882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882D75831DE507320033F95F /* MessagesDataSource.swift */; };
|
||||
888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888CEBFB1D3FD525005178DE /* MessagesViewController.swift */; };
|
||||
@@ -17,9 +20,11 @@
|
||||
88916B401CF0DF5100469F91 /* MessageKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 88916B3E1CF0DF5100469F91 /* MessageKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
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 */; };
|
||||
B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; };
|
||||
B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; };
|
||||
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 */; };
|
||||
@@ -31,10 +36,11 @@
|
||||
B074EE951F35588A00ABB8C8 /* MessageFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */; };
|
||||
B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */; };
|
||||
B074EEA81F3971A600ABB8C8 /* MessageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */; };
|
||||
B074EEAA1F3BE8F500ABB8C8 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EEA91F3BE8F300ABB8C8 /* NSAttributedString+Extensions.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 */; };
|
||||
B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438F1F289142004D0129 /* UIColor+Extensions.swift */; };
|
||||
B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */; };
|
||||
B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -51,6 +57,9 @@
|
||||
171D5AB81F36712B0053DF69 /* InputTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = "<group>"; };
|
||||
372F6AEA1F36C15600B57FBD /* AvatarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = "<group>"; };
|
||||
372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarViewTests.swift; sourceTree = "<group>"; };
|
||||
376AD1811F4258D80083072A /* TestMessageModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestMessageModel.swift; sourceTree = "<group>"; };
|
||||
376AD1851F4259270083072A /* MessagesDisplayDataSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDataSourceTests.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -61,9 +70,11 @@
|
||||
88916B421CF0DF5900469F91 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -75,10 +86,11 @@
|
||||
B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageFooterView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
B074EEA91F3BE8F300ABB8C8 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.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>"; };
|
||||
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>"; };
|
||||
B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -100,6 +112,15 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
376AD1801F4258C50083072A /* Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
376AD1811F4258D80083072A /* TestMessageModel.swift */,
|
||||
376AD1871F4259D20083072A /* TestMessagesViewControllerModel.swift */,
|
||||
);
|
||||
name = "Support Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
88916B181CF0DF2F00469F91 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -138,6 +159,8 @@
|
||||
88916B421CF0DF5900469F91 /* Info.plist */,
|
||||
88916B431CF0DF5900469F91 /* MessageKitTests.swift */,
|
||||
372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */,
|
||||
376AD1851F4259270083072A /* MessagesDisplayDataSourceTests.swift */,
|
||||
376AD1801F4258C50083072A /* Support Files */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
@@ -147,7 +170,7 @@
|
||||
children = (
|
||||
B09643851F286C9E004D0129 /* String+Extensions.swift */,
|
||||
B096438F1F289142004D0129 /* UIColor+Extensions.swift */,
|
||||
B074EEA91F3BE8F300ABB8C8 /* NSAttributedString+Extensions.swift */,
|
||||
B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */,
|
||||
);
|
||||
name = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -159,6 +182,8 @@
|
||||
B0655A2B1F23D81600542A83 /* MessageData.swift */,
|
||||
37C936971F38F6AC00853DF2 /* Avatar.swift */,
|
||||
B0655A271F23D71400542A83 /* MessageDirection.swift */,
|
||||
B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */,
|
||||
B05530B41F493CFA008BB420 /* DetectorType.swift */,
|
||||
);
|
||||
name = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -187,6 +212,7 @@
|
||||
B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */,
|
||||
B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */,
|
||||
B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */,
|
||||
B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */,
|
||||
);
|
||||
name = Protocols;
|
||||
sourceTree = "<group>";
|
||||
@@ -344,7 +370,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
171D5AB91F36712B0053DF69 /* InputTextView.swift in Sources */,
|
||||
B074EEAA1F3BE8F500ABB8C8 /* NSAttributedString+Extensions.swift in Sources */,
|
||||
B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */,
|
||||
882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */,
|
||||
888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */,
|
||||
@@ -353,16 +378,22 @@
|
||||
B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */,
|
||||
B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */,
|
||||
B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */,
|
||||
B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */,
|
||||
376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */,
|
||||
B0655A2A1F23D77200542A83 /* Sender.swift in Sources */,
|
||||
B074EE931F35587100ABB8C8 /* MessageHeaderView.swift in Sources */,
|
||||
376AD1821F4258D80083072A /* TestMessageModel.swift in Sources */,
|
||||
B0655A4D1F244C0600542A83 /* MessageCollectionViewCell.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 */,
|
||||
B074EEA81F3971A600ABB8C8 /* MessageLabel.swift in Sources */,
|
||||
372F6AEB1F36C15600B57FBD /* AvatarView.swift in Sources */,
|
||||
B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */,
|
||||
88916B471CF0DFE600469F91 /* MessageType.swift in Sources */,
|
||||
B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */,
|
||||
B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */,
|
||||
37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */,
|
||||
B096438E1F2890FB004D0129 /* MessagesDisplayDataSource.swift in Sources */,
|
||||
@@ -375,6 +406,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
88916B451CF0DF5900469F91 /* MessageKitTests.swift in Sources */,
|
||||
376AD1861F4259270083072A /* MessagesDisplayDataSourceTests.swift in Sources */,
|
||||
372F6AEF1F36C61000B57FBD /* AvatarViewTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -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 AvatarPosition {
|
||||
|
||||
case cellTop
|
||||
case messageTop
|
||||
case messageCenter
|
||||
case messageBottom
|
||||
case cellBottom
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 DetectorType {
|
||||
|
||||
case address
|
||||
case date
|
||||
case phoneNumber
|
||||
case url
|
||||
|
||||
// MARK: - Not supported yet
|
||||
|
||||
//case mention
|
||||
//case hashtag
|
||||
//case custom
|
||||
|
||||
var textCheckingType: NSTextCheckingResult.CheckingType {
|
||||
switch self {
|
||||
case .address: return .address
|
||||
case .date: return .date
|
||||
case .phoneNumber: return .phoneNumber
|
||||
case .url: return .link
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,10 @@ public protocol MessageCellDelegate: class {
|
||||
|
||||
func didTapAvatar(in cell: MessageCollectionViewCell)
|
||||
|
||||
func didTapBottomLabel(in cell: MessageCollectionViewCell)
|
||||
|
||||
func didTapTopLabel(in cell: MessageCollectionViewCell)
|
||||
|
||||
}
|
||||
|
||||
extension MessageCellDelegate {
|
||||
@@ -38,4 +42,8 @@ extension MessageCellDelegate {
|
||||
|
||||
func didTapAvatar(in cell: MessageCollectionViewCell) {}
|
||||
|
||||
func didTapBottomLabel(in cell: MessageCollectionViewCell) {}
|
||||
|
||||
func didTapTopLabel(in cell: MessageCollectionViewCell) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
open var avatarView: AvatarView = AvatarView()
|
||||
|
||||
open var cellTopLabel: MessageLabel = MessageLabel()
|
||||
open var cellTopLabel: MessageLabel = {
|
||||
let topLabel = MessageLabel()
|
||||
topLabel.enabledDetectors = []
|
||||
return topLabel
|
||||
}()
|
||||
|
||||
open var messageLabel: MessageLabel = MessageLabel()
|
||||
|
||||
open var cellBottomLabel: MessageLabel = MessageLabel()
|
||||
open var cellBottomLabel: MessageLabel = {
|
||||
let bottomLabel = MessageLabel()
|
||||
bottomLabel.enabledDetectors = []
|
||||
return bottomLabel
|
||||
}()
|
||||
|
||||
open weak var delegate: MessageCellDelegate?
|
||||
|
||||
@@ -102,7 +110,7 @@ open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
if !attributes.topLabelExtendsPastAvatar {
|
||||
if attributes.topLabelExtendsPastAvatar == false {
|
||||
origin = CGPoint(x: attributes.avatarSize.width + attributes.avatarMessagePadding, y: 0)
|
||||
}
|
||||
|
||||
@@ -113,7 +121,7 @@ open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0, y: contentView.frame.height - attributes.cellBottomLabelSize.height)
|
||||
|
||||
if !attributes.bottomLabelExtendsPastAvatar {
|
||||
if attributes.bottomLabelExtendsPastAvatar == false {
|
||||
origin.x = attributes.avatarSize.width + attributes.avatarMessagePadding
|
||||
}
|
||||
|
||||
@@ -143,14 +151,34 @@ open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
let yPosition = contentView.frame.height - attributes.avatarSize.height - attributes.avatarBottomPadding - attributes.cellBottomLabelSize.height
|
||||
switch attributes.avatarPosition {
|
||||
case .cellTop:
|
||||
if attributes.topLabelExtendsPastAvatar {
|
||||
origin.y = attributes.cellTopLabelSize.height
|
||||
} else {
|
||||
origin.y = 0
|
||||
}
|
||||
case .cellBottom:
|
||||
if attributes.bottomLabelExtendsPastAvatar {
|
||||
origin.y = contentView.frame.height - attributes.avatarSize.height - attributes.cellBottomLabelSize.height
|
||||
} else {
|
||||
origin.y = contentView.frame.height - attributes.avatarSize.height
|
||||
}
|
||||
case .messageTop:
|
||||
origin.y = attributes.cellTopLabelSize.height
|
||||
case .messageBottom:
|
||||
origin.y = contentView.frame.height - attributes.avatarSize.height - attributes.cellBottomLabelSize.height
|
||||
case .messageCenter:
|
||||
let messageMidY = (attributes.messageContainerSize.height / 2)
|
||||
let avatarMidY = (attributes.avatarSize.height / 2)
|
||||
origin.y = contentView.frame.height - attributes.cellTopLabelSize.height - messageMidY - avatarMidY
|
||||
}
|
||||
|
||||
switch attributes.direction {
|
||||
case .outgoing:
|
||||
let xPosition = contentView.frame.width - attributes.avatarSize.width
|
||||
origin = CGPoint(x: xPosition, y: yPosition)
|
||||
origin.x = contentView.frame.width - attributes.avatarSize.width
|
||||
case .incoming:
|
||||
origin = CGPoint(x: 0, y: yPosition)
|
||||
origin.x = 0
|
||||
}
|
||||
|
||||
return CGRect(origin: origin, size: attributes.avatarSize)
|
||||
@@ -176,6 +204,15 @@ open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
let messageTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapMessage))
|
||||
messageContainerView.addGestureRecognizer(messageTapGesture)
|
||||
messageTapGesture.delegate = messageLabel
|
||||
|
||||
let topLabelTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTopLabel))
|
||||
cellTopLabel.addGestureRecognizer(topLabelTapGesture)
|
||||
cellTopLabel.isUserInteractionEnabled = true
|
||||
|
||||
let bottomlabelTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBottomLabel))
|
||||
cellBottomLabel.addGestureRecognizer(bottomlabelTapGesture)
|
||||
cellBottomLabel.isUserInteractionEnabled = true
|
||||
|
||||
}
|
||||
|
||||
@@ -188,4 +225,12 @@ open class MessageCollectionViewCell: UICollectionViewCell {
|
||||
func didTapMessage() {
|
||||
delegate?.didTapMessage(in: self)
|
||||
}
|
||||
|
||||
func didTapTopLabel() {
|
||||
delegate?.didTapTopLabel(in: self)
|
||||
}
|
||||
|
||||
func didTapBottomLabel() {
|
||||
delegate?.didTapBottomLabel(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
+395
-7
@@ -24,9 +24,86 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MessageLabel: UILabel {
|
||||
open class MessageLabel: UILabel, UIGestureRecognizerDelegate {
|
||||
|
||||
// MARK: - Properties
|
||||
// MARK: - Private Properties
|
||||
|
||||
private lazy var layoutManager: NSLayoutManager = {
|
||||
let layoutManager = NSLayoutManager()
|
||||
layoutManager.addTextContainer(self.textContainer)
|
||||
return layoutManager
|
||||
}()
|
||||
|
||||
private lazy var textContainer: NSTextContainer = {
|
||||
let textContainer = NSTextContainer()
|
||||
textContainer.lineFragmentPadding = 0
|
||||
textContainer.maximumNumberOfLines = self.numberOfLines
|
||||
textContainer.lineBreakMode = self.lineBreakMode
|
||||
textContainer.size = self.bounds.size
|
||||
return textContainer
|
||||
}()
|
||||
|
||||
private lazy var textStorage: NSTextStorage = {
|
||||
let textStorage = NSTextStorage()
|
||||
textStorage.addLayoutManager(self.layoutManager)
|
||||
return textStorage
|
||||
}()
|
||||
|
||||
private lazy var rangesForDetectors: [DetectorType: [(NSRange, Any?)]] = [:]
|
||||
|
||||
// MARK: - Public Properties
|
||||
|
||||
open weak var delegate: MessageLabelDelegate?
|
||||
|
||||
open var enabledDetectors: [DetectorType] = [.phoneNumber, .address, .date, .url]
|
||||
|
||||
open override var attributedText: NSAttributedString? {
|
||||
didSet {
|
||||
guard attributedText != oldValue else { return }
|
||||
setTextStorage()
|
||||
}
|
||||
}
|
||||
|
||||
open override var text: String? {
|
||||
didSet {
|
||||
guard text != oldValue else { return }
|
||||
setTextStorage()
|
||||
}
|
||||
}
|
||||
|
||||
open override var font: UIFont! {
|
||||
didSet {
|
||||
guard font != oldValue else { return }
|
||||
guard let attributedText = attributedText else { return }
|
||||
textStorage.setAttributedString(attributedText)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open override var textColor: UIColor! {
|
||||
didSet {
|
||||
guard textColor != oldValue else { return }
|
||||
guard let attributedText = attributedText else { return }
|
||||
textStorage.setAttributedString(attributedText)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open override var lineBreakMode: NSLineBreakMode {
|
||||
didSet {
|
||||
guard lineBreakMode != oldValue else { return }
|
||||
textContainer.lineBreakMode = lineBreakMode
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open override var numberOfLines: Int {
|
||||
didSet {
|
||||
guard numberOfLines != oldValue else { return }
|
||||
textContainer.maximumNumberOfLines = numberOfLines
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open var textInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
@@ -35,19 +112,330 @@ open class MessageLabel: UILabel {
|
||||
}
|
||||
}
|
||||
|
||||
open var addressAttributes: [String: Any] = [:] {
|
||||
didSet {
|
||||
updateAttributes(for: .address)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open var dateAttributes: [String: Any] = [:] {
|
||||
didSet {
|
||||
updateAttributes(for: .date)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open var phoneNumberAttributes: [String: Any] = [:] {
|
||||
didSet {
|
||||
updateAttributes(for: .phoneNumber)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
open var urlAttributes: [String: Any] = [:] {
|
||||
didSet {
|
||||
updateAttributes(for: .url)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
numberOfLines = 0
|
||||
|
||||
// Message Label Specific
|
||||
self.numberOfLines = 0
|
||||
self.lineBreakMode = .byWordWrapping
|
||||
|
||||
let defaultAttributes: [String: Any] = [
|
||||
NSForegroundColorAttributeName: self.textColor,
|
||||
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
|
||||
NSUnderlineColorAttributeName: self.textColor
|
||||
]
|
||||
|
||||
self.addressAttributes = defaultAttributes
|
||||
self.dateAttributes = defaultAttributes
|
||||
self.phoneNumberAttributes = defaultAttributes
|
||||
self.urlAttributes = defaultAttributes
|
||||
|
||||
setupGestureRecognizers()
|
||||
|
||||
}
|
||||
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
// MARK: - Open Methods
|
||||
|
||||
open override func draw(_ rect: CGRect) {
|
||||
super.drawText(in: UIEdgeInsetsInsetRect(rect, textInsets))
|
||||
open override func drawText(in rect: CGRect) {
|
||||
|
||||
let insetRect = UIEdgeInsetsInsetRect(rect, textInsets)
|
||||
textContainer.size = CGSize(width: insetRect.width, height: rect.height)
|
||||
|
||||
let origin = insetRect.origin
|
||||
let range = layoutManager.glyphRange(for: textContainer)
|
||||
|
||||
layoutManager.drawBackground(forGlyphRange: range, at: origin)
|
||||
layoutManager.drawGlyphs(forGlyphRange: range, at: origin)
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
// MARK: UIGestureRecognizer Delegate
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//swiftlint:disable cyclomatic_complexity
|
||||
// Yeah we're disabling this because the whole file is a mess :D
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
|
||||
let touchLocation = touch.location(in: self)
|
||||
|
||||
switch true {
|
||||
case gestureRecognizer.view != self.superview && gestureRecognizer.view != self:
|
||||
return true
|
||||
case gestureRecognizer.view == self.superview:
|
||||
guard let index = stringIndex(at: touchLocation) else { return true }
|
||||
for (_, ranges) in rangesForDetectors {
|
||||
for (nsRange, _) in ranges {
|
||||
guard let range = nsRange.toRange() else { return true }
|
||||
if range.contains(index) { return false }
|
||||
}
|
||||
}
|
||||
return true
|
||||
case gestureRecognizer.view == self:
|
||||
guard let index = stringIndex(at: touchLocation) else { return false }
|
||||
for (_, ranges) in rangesForDetectors {
|
||||
for (nsRange, _) in ranges {
|
||||
guard let range = nsRange.toRange() else { return false }
|
||||
if range.contains(index) { return true }
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func setTextStorage() {
|
||||
|
||||
// Anytime we update the text storage we need to clear the previous ranges
|
||||
rangesForDetectors.removeAll()
|
||||
|
||||
guard let attributedText = attributedText, attributedText.length > 0 else {
|
||||
textStorage.setAttributedString(NSAttributedString())
|
||||
setNeedsDisplay()
|
||||
return
|
||||
}
|
||||
|
||||
guard let checkingResults = parse(text: attributedText, for: enabledDetectors), checkingResults.isEmpty == false else {
|
||||
textStorage.setAttributedString(attributedText)
|
||||
setNeedsDisplay()
|
||||
return
|
||||
}
|
||||
|
||||
setRangesForDetectors(in: checkingResults)
|
||||
|
||||
let textWithDetectorAttributes = addDetectorAttributes(to: attributedText, for: checkingResults)
|
||||
|
||||
textStorage.setAttributedString(textWithDetectorAttributes)
|
||||
|
||||
setNeedsDisplay()
|
||||
|
||||
}
|
||||
|
||||
private func addDetectorAttributes(to text: NSAttributedString, for checkingResults: [NSTextCheckingResult]) -> NSAttributedString {
|
||||
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: text)
|
||||
|
||||
checkingResults.forEach { result in
|
||||
let attributes = detectorAttributes(for: result.resultType)
|
||||
mutableAttributedString.addAttributes(attributes, range: result.range)
|
||||
}
|
||||
|
||||
return mutableAttributedString
|
||||
}
|
||||
|
||||
private func updateAttributes(for detectorType: DetectorType) {
|
||||
|
||||
guard let attributedText = attributedText, attributedText.length > 0 else { return }
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText)
|
||||
|
||||
guard let ranges = rangesForDetectors[detectorType] else { return }
|
||||
|
||||
ranges.forEach { (range, _) in
|
||||
let attributes = detectorAttributes(for: detectorType)
|
||||
mutableAttributedString.addAttributes(attributes, range: range)
|
||||
}
|
||||
|
||||
textStorage.setAttributedString(mutableAttributedString)
|
||||
|
||||
}
|
||||
|
||||
private func detectorAttributes(for detectorType: DetectorType) -> [String: Any] {
|
||||
|
||||
switch detectorType {
|
||||
case .address:
|
||||
return addressAttributes
|
||||
case .date:
|
||||
return dateAttributes
|
||||
case .phoneNumber:
|
||||
return phoneNumberAttributes
|
||||
case .url:
|
||||
return urlAttributes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func detectorAttributes(for checkingResultType: NSTextCheckingResult.CheckingType) -> [String: Any] {
|
||||
switch checkingResultType {
|
||||
case NSTextCheckingResult.CheckingType.address:
|
||||
return addressAttributes
|
||||
case NSTextCheckingResult.CheckingType.date:
|
||||
return dateAttributes
|
||||
case NSTextCheckingResult.CheckingType.phoneNumber:
|
||||
return phoneNumberAttributes
|
||||
case NSTextCheckingResult.CheckingType.link:
|
||||
return urlAttributes
|
||||
default:
|
||||
fatalError("Received an unrecognized NSTextCheckingResult.CheckingType")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Parsing Text
|
||||
|
||||
private func parse(text: NSAttributedString, for detectorTypes: [DetectorType]) -> [NSTextCheckingResult]? {
|
||||
guard detectorTypes.isEmpty == false else { return nil }
|
||||
let checkingTypes = detectorTypes.reduce(0) { $0 | $1.textCheckingType.rawValue }
|
||||
let detector = try? NSDataDetector(types: checkingTypes)
|
||||
|
||||
return detector?.matches(in: text.string, options: [], range: NSRange(location: 0, length: text.length))
|
||||
}
|
||||
|
||||
private func setRangesForDetectors(in checkingResults: [NSTextCheckingResult]) {
|
||||
|
||||
for result in checkingResults {
|
||||
|
||||
switch result.resultType {
|
||||
case NSTextCheckingResult.CheckingType.address:
|
||||
var ranges = rangesForDetectors[.address] ?? []
|
||||
let tuple = (result.range, result.addressComponents) as (NSRange, Any?)
|
||||
ranges.append(tuple)
|
||||
rangesForDetectors.updateValue(ranges, forKey: .address)
|
||||
case NSTextCheckingResult.CheckingType.date:
|
||||
var ranges = rangesForDetectors[.date] ?? []
|
||||
let tuple = (result.range, result.date) as (NSRange, Any?)
|
||||
ranges.append(tuple)
|
||||
rangesForDetectors.updateValue(ranges, forKey: .date)
|
||||
case NSTextCheckingResult.CheckingType.phoneNumber:
|
||||
var ranges = rangesForDetectors[.phoneNumber] ?? []
|
||||
let tuple = (result.range, result.phoneNumber) as (NSRange, Any?)
|
||||
ranges.append(tuple)
|
||||
rangesForDetectors.updateValue(ranges, forKey: .phoneNumber)
|
||||
case NSTextCheckingResult.CheckingType.link:
|
||||
var ranges = rangesForDetectors[.url] ?? []
|
||||
let tuple = (result.range, result.url) as (NSRange, Any?)
|
||||
ranges.append(tuple)
|
||||
rangesForDetectors.updateValue(ranges, forKey: .url)
|
||||
default:
|
||||
fatalError("Received an unrecognized NSTextCheckingResult.CheckingType")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Gesture Handling
|
||||
|
||||
private func stringIndex(at location: CGPoint) -> Int? {
|
||||
guard textStorage.length > 0 else { return nil }
|
||||
|
||||
var location = location
|
||||
let textOffset = CGPoint(x: textInsets.left, y: textInsets.right)
|
||||
|
||||
location.x -= textOffset.x
|
||||
location.y -= textOffset.y
|
||||
|
||||
let glyphIndex = layoutManager.glyphIndex(for: location, in: textContainer)
|
||||
|
||||
let lineRect = layoutManager.lineFragmentUsedRect(forGlyphAt: glyphIndex, effectiveRange: nil)
|
||||
|
||||
if lineRect.contains(location) {
|
||||
return layoutManager.characterIndexForGlyph(at: glyphIndex)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func setupGestureRecognizers() {
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
|
||||
addGestureRecognizer(tapGesture)
|
||||
tapGesture.delegate = self
|
||||
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
|
||||
addGestureRecognizer(longPressGesture)
|
||||
tapGesture.delegate = self
|
||||
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
func handleGesture(_ gesture: UIGestureRecognizer) {
|
||||
|
||||
let touchLocation = gesture.location(ofTouch: 0, in: self)
|
||||
guard let index = stringIndex(at: touchLocation) else { return }
|
||||
|
||||
for (detectorType, ranges) in rangesForDetectors {
|
||||
for (nsRange, value) in ranges {
|
||||
guard let range = nsRange.toRange() else { return }
|
||||
if range.contains(index) {
|
||||
handleGesture(for: detectorType, value: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleGesture(for detectorType: DetectorType, value: Any?) {
|
||||
|
||||
switch detectorType {
|
||||
case .address:
|
||||
guard let addressComponents = value as? [String: String] else { return }
|
||||
handleAddress(addressComponents)
|
||||
case .phoneNumber:
|
||||
guard let phoneNumber = value as? String else { return }
|
||||
handlePhoneNumber(phoneNumber)
|
||||
case .date:
|
||||
guard let date = value as? Date else { return }
|
||||
handleDate(date)
|
||||
case .url:
|
||||
guard let url = value as? URL else { return }
|
||||
handleURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleAddress(_ addressComponents: [String: String]) {
|
||||
delegate?.didSelectAddress(addressComponents)
|
||||
}
|
||||
|
||||
private func handleDate(_ date: Date) {
|
||||
delegate?.didSelectDate(date)
|
||||
}
|
||||
|
||||
private func handleURL(_ url: URL) {
|
||||
delegate?.didSelectURL(url)
|
||||
}
|
||||
|
||||
private func handlePhoneNumber(_ phoneNumber: String) {
|
||||
delegate?.didSelectPhoneNumber(phoneNumber)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 protocol MessageLabelDelegate: class {
|
||||
|
||||
func didSelectAddress(_ addressComponents: [String: String])
|
||||
|
||||
func didSelectDate(_ date: Date)
|
||||
|
||||
func didSelectPhoneNumber(_ phoneNumber: String)
|
||||
|
||||
func didSelectURL(_ url: URL)
|
||||
|
||||
}
|
||||
|
||||
extension MessageLabelDelegate {
|
||||
|
||||
func didSelectAddress(_ addressComponents: [String: String]) {}
|
||||
|
||||
func didSelectDate(_ date: Date) {}
|
||||
|
||||
func didSelectPhoneNumber(_ phoneNumber: String) {}
|
||||
|
||||
func didSelectURL(_ url: URL) {}
|
||||
|
||||
}
|
||||
@@ -34,10 +34,12 @@ open class MessagesCollectionView: UICollectionView {
|
||||
|
||||
open weak var messageCellDelegate: MessageCellDelegate?
|
||||
|
||||
open weak var messageLabelDelegate: MessageLabelDelegate?
|
||||
|
||||
private var indexPathForLastItem: IndexPath? {
|
||||
|
||||
let lastSection = numberOfSections > 0 ? numberOfSections - 1 : 0
|
||||
guard numberOfItems(inSection: lastSection) > 0 else { return nil }
|
||||
guard lastSection > 0, numberOfItems(inSection: lastSection) > 0 else { return nil }
|
||||
return IndexPath(item: numberOfItems(inSection: lastSection) - 1, section: lastSection)
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
open var incomingAvatarSize: CGSize
|
||||
open var outgoingAvatarSize: CGSize
|
||||
|
||||
fileprivate var avatarBottomPadding: CGFloat = 2
|
||||
fileprivate var avatarMessagePadding: CGFloat = 4
|
||||
|
||||
fileprivate var messagesCollectionView: MessagesCollectionView? {
|
||||
@@ -114,6 +113,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
|
||||
guard let messagesCollectionView = messagesCollectionView else { return }
|
||||
guard let dataSource = messagesCollectionView.messagesDataSource else { return }
|
||||
guard let displayDataSource = dataSource as? MessagesDisplayDataSource else { return }
|
||||
|
||||
let indexPath = attributes.indexPath
|
||||
|
||||
@@ -133,9 +133,11 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
attributes.bottomLabelExtendsPastAvatar = bottomLabelExtendsPastAvatar
|
||||
|
||||
attributes.avatarSize = avatarSize(for: message)
|
||||
attributes.avatarBottomPadding = avatarBottomPadding
|
||||
attributes.avatarMessagePadding = avatarMessagePadding
|
||||
|
||||
let avatarPosition = displayDataSource.avatarPosition(for: message, at: indexPath, in: messagesCollectionView)
|
||||
attributes.avatarPosition = avatarPosition
|
||||
|
||||
attributes.direction = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming
|
||||
|
||||
}
|
||||
@@ -230,7 +232,7 @@ extension MessagesCollectionViewFlowLayout {
|
||||
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)
|
||||
let availableWidth = cellTopLabelWidth(for: message) - cellTopLabelInsets.left - cellTopLabelInsets.right
|
||||
|
||||
let estimatedHeight = topLabelText.height(considering: availableWidth)
|
||||
|
||||
@@ -244,7 +246,7 @@ extension MessagesCollectionViewFlowLayout {
|
||||
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)
|
||||
let availableWidth = cellBottomLabelWidth(for: message) - cellBottomLabelInsets.left - cellBottomLabelInsets.right
|
||||
|
||||
let estimatedHeight = bottomLabelText.height(considering: availableWidth)
|
||||
|
||||
@@ -254,11 +256,11 @@ extension MessagesCollectionViewFlowLayout {
|
||||
private func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
let size = avatarSize(for: message)
|
||||
let avatarHeightPlusBottomPadding = size.height == 0 ? 0 : size.height + avatarBottomPadding
|
||||
let avatarHeight = size.height
|
||||
let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath)
|
||||
let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
|
||||
|
||||
let minimumHeight = topLabelHeight + avatarHeightPlusBottomPadding + bottomLabelHeight
|
||||
let minimumHeight = topLabelHeight + avatarHeight + bottomLabelHeight
|
||||
|
||||
return minimumHeight
|
||||
|
||||
@@ -294,12 +296,12 @@ extension MessagesCollectionViewFlowLayout {
|
||||
|
||||
switch message.data {
|
||||
case .text(let text):
|
||||
estimatedWidth = text.width(considering: containerHeight, and: messageLabelFont)
|
||||
estimatedWidth = text.width(considering: containerHeight, and: messageLabelFont).rounded(.up)
|
||||
case .attributedText(let text):
|
||||
estimatedWidth = text.width(considering: containerHeight)
|
||||
estimatedWidth = text.width(considering: containerHeight).rounded(.up)
|
||||
}
|
||||
|
||||
let widthToUse = estimatedWidth.rounded(.up) > availableWidth ? availableWidth : estimatedWidth
|
||||
let widthToUse = estimatedWidth > availableWidth ? availableWidth : estimatedWidth
|
||||
|
||||
let finalWidth = widthToUse + horizontalMessageInsets
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib
|
||||
var bottomLabelExtendsPastAvatar = false
|
||||
|
||||
var avatarSize: CGSize = CGSize(width: 30, height: 30)
|
||||
var avatarBottomPadding: CGFloat = 4.0
|
||||
var avatarPosition: AvatarPosition = .cellBottom
|
||||
var avatarMessagePadding: CGFloat = 4.0
|
||||
|
||||
var direction: MessageDirection = .incoming
|
||||
@@ -63,7 +63,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib
|
||||
copy.cellBottomLabelInsets = cellBottomLabelInsets
|
||||
copy.bottomLabelExtendsPastAvatar = bottomLabelExtendsPastAvatar
|
||||
copy.avatarSize = avatarSize
|
||||
copy.avatarBottomPadding = avatarBottomPadding
|
||||
copy.avatarPosition = avatarPosition
|
||||
copy.avatarMessagePadding = avatarMessagePadding
|
||||
copy.direction = direction
|
||||
return copy
|
||||
|
||||
@@ -25,41 +25,57 @@
|
||||
import Foundation
|
||||
|
||||
public protocol MessagesDisplayDataSource: class, MessagesDataSource {
|
||||
|
||||
func messageColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
|
||||
|
||||
func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar
|
||||
|
||||
|
||||
func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
|
||||
|
||||
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 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 {
|
||||
|
||||
func messageColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
|
||||
|
||||
func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
|
||||
return isFromCurrentSender(message: message) ? .white : .black
|
||||
}
|
||||
|
||||
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 avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
|
||||
return Avatar()
|
||||
}
|
||||
|
||||
func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
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,26 +1,26 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -121,8 +121,6 @@ open class MessagesViewController: UIViewController {
|
||||
|
||||
// MARK: - UICollectionViewDelegate & UICollectionViewDelegateFlowLayout Conformance
|
||||
|
||||
//swiftlint:disable line_length
|
||||
|
||||
extension MessagesViewController: UICollectionViewDelegateFlowLayout {
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
@@ -157,23 +155,35 @@ extension MessagesViewController: UICollectionViewDataSource {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MessageCell", for: indexPath) as? MessageCollectionViewCell ?? MessageCollectionViewCell()
|
||||
|
||||
guard let messagesCollectionView = collectionView as? MessagesCollectionView else { return cell }
|
||||
guard let messageCellDelegate = messagesCollectionView.messageCellDelegate else { return cell }
|
||||
|
||||
cell.delegate = messageCellDelegate
|
||||
if let cellDelegate = messagesCollectionView.messageCellDelegate {
|
||||
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 }
|
||||
guard let displayDataSource = messagesDataSource as? MessagesDisplayDataSource else { return cell }
|
||||
|
||||
let message = displayDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
let messageColor = displayDataSource.messageColor(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 message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
||||
|
||||
if let displayDataSource = messagesDataSource as? MessagesDisplayDataSource {
|
||||
|
||||
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)
|
||||
|
||||
cell.avatarView.set(avatar: avatar)
|
||||
cell.messageLabel.textColor = textColor
|
||||
cell.messageContainerView.backgroundColor = messageColor
|
||||
cell.cellTopLabel.attributedText = topLabelText
|
||||
cell.cellBottomLabel.attributedText = bottomLabelText
|
||||
|
||||
}
|
||||
|
||||
cell.cellTopLabel.attributedText = topLabelText
|
||||
cell.cellBottomLabel.attributedText = bottomLabelText
|
||||
cell.avatarView.set(avatar: avatar)
|
||||
cell.messageContainerView.backgroundColor = messageColor
|
||||
cell.configure(with: message)
|
||||
|
||||
return cell
|
||||
@@ -222,37 +232,40 @@ extension MessagesViewController: UICollectionViewDataSource {
|
||||
|
||||
// MARK: - Keyboard Handling
|
||||
extension MessagesViewController {
|
||||
|
||||
fileprivate func addKeyboardObservers() {
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillHide), name: .UIKeyboardWillHide, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil)
|
||||
|
||||
|
||||
fileprivate func addKeyboardObservers() {
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillHide), name: .UIKeyboardWillHide, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: .UIKeyboardWillShow, object: nil)
|
||||
|
||||
}
|
||||
|
||||
|
||||
fileprivate func removeKeyboardObservers() {
|
||||
|
||||
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillChangeFrame, object: nil)
|
||||
|
||||
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
|
||||
}
|
||||
|
||||
func handleKeyboardWillHide(_ notification: Notification) {
|
||||
|
||||
messagesCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
|
||||
}
|
||||
|
||||
func handleKeyboardWillChangeFrame(_ notification: Notification) {
|
||||
|
||||
guard let keyboardSizeValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
|
||||
|
||||
let keyboardRect = keyboardSizeValue.cgRectValue
|
||||
let messageInputBarHeight = inputAccessoryView?.bounds.size.height ?? 0
|
||||
let keyboardHeight = keyboardRect.height - messageInputBarHeight
|
||||
print(keyboardHeight)
|
||||
messagesCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func handleKeyboardWillShow(_ notification: Notification) {
|
||||
guard let keyboardEndSizeValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
|
||||
|
||||
let keyboardFrame = keyboardEndSizeValue.cgRectValue
|
||||
let keyboard = self.view.convert(keyboardFrame, from: self.view.window)
|
||||
let height = self.view.frame.size.height
|
||||
let messageInputBarHeight = inputAccessoryView?.bounds.size.height ?? 0
|
||||
let keyboardHeight = keyboardFrame.height - messageInputBarHeight
|
||||
|
||||
if (keyboard.origin.y + keyboard.size.height) > height {
|
||||
// Hardware keyboard is found
|
||||
messagesCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
|
||||
} else {
|
||||
// Software keyboard is found
|
||||
messagesCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
|
||||
}
|
||||
}
|
||||
|
||||
func handleKeyboardWillHide(_ notification: Notification) {
|
||||
messagesCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,5 +41,4 @@ extension NSAttributedString {
|
||||
return rect.width
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 XCTest
|
||||
|
||||
@testable import MessageKit
|
||||
|
||||
class MessagesDisplayDataSourceTests: XCTestCase {
|
||||
|
||||
let testClass = TestMessagesViewControllerModel()
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Ensures that the messageList has been set.
|
||||
testClass.viewDidLoad()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testInit() {
|
||||
XCTAssertNotNil(testClass)
|
||||
XCTAssertNotNil(testClass.messageList)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func testBackGroundColorDefaultState() {
|
||||
XCTAssertEqual(testClass.backgroundColor(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0), in: testClass.messagesCollectionView), UIColor.outgoingGreen)
|
||||
XCTAssertNotEqual(testClass.backgroundColor(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0), in: testClass.messagesCollectionView), UIColor.incomingGray)
|
||||
XCTAssertEqual(testClass.backgroundColor(for: testClass.messageList[1], at: IndexPath(item: 1, section: 0), in: testClass.messagesCollectionView), UIColor.incomingGray)
|
||||
XCTAssertNotEqual(testClass.backgroundColor(for: testClass.messageList[1], at: IndexPath(item: 1, section: 0), in: testClass.messagesCollectionView), UIColor.outgoingGreen)
|
||||
}
|
||||
|
||||
func testAvatarDefaultState() {
|
||||
XCTAssertNotNil(testClass.avatar(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0), in: testClass.messagesCollectionView).initals)
|
||||
}
|
||||
|
||||
func testCellTopLabelDefaultState() {
|
||||
XCTAssertNil(testClass.cellTopLabelAttributedText(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0)))
|
||||
}
|
||||
|
||||
func testCellBottomLabelDefaultState() {
|
||||
XCTAssertNil(testClass.cellBottomLabelAttributedText(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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
|
||||
|
||||
struct TestMessage: MessageType {
|
||||
var messageId: String
|
||||
var sender: Sender
|
||||
var sentDate: Date
|
||||
var data: MessageData
|
||||
|
||||
init(text: String, sender: Sender, messageId: String) {
|
||||
data = .text(text)
|
||||
self.sender = sender
|
||||
self.messageId = messageId
|
||||
self.sentDate = Date()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
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
|
||||
|
||||
class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDataSource {
|
||||
|
||||
var messageList: [TestMessage] = []
|
||||
|
||||
static let sender1 = Sender(id: "1", displayName: "Dan")
|
||||
static let sender2 = Sender(id: "2", displayName: "jobs")
|
||||
|
||||
let testMessage1 = TestMessage(text: "Hi", sender: sender1, messageId: "asdf")
|
||||
let testMessage2 = TestMessage(text: "sup", sender: sender2, messageId: "dddf")
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
messageList = [testMessage1, testMessage2]
|
||||
self.messagesCollectionView.messagesDataSource = self
|
||||
}
|
||||
}
|
||||
|
||||
// - MARK: MessagesDataSource conformace
|
||||
extension TestMessagesViewControllerModel: MessagesDataSource {
|
||||
|
||||
func currentSender() -> Sender {
|
||||
return Sender(id: "1", displayName: "Dan")
|
||||
}
|
||||
|
||||
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int {
|
||||
return messageList.count
|
||||
}
|
||||
|
||||
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
|
||||
return messageList[indexPath.section]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user