Merge pull request #70 from MessageKit/v0.5.0

v0.5.0
This commit is contained in:
Steven Deutsch
2017-08-24 21:12:33 -05:00
committed by GitHub
22 changed files with 977 additions and 118 deletions
+2
View File
@@ -3,3 +3,5 @@ disabled_rules:
- identifier_name
- trailing_whitespace
- line_length
- type_body_length
- file_length
+8
View File
@@ -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
View File
@@ -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
+10 -3
View File
@@ -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 whats 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
View File
@@ -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.'
+36 -4
View File
@@ -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;
+35
View File
@@ -0,0 +1,35 @@
/*
MIT License
Copyright (c) 2017 MessageKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import Foundation
public enum AvatarPosition {
case cellTop
case messageTop
case messageCenter
case messageBottom
case cellBottom
}
+49
View File
@@ -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
}
}
}
+8
View File
@@ -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) {}
}
+53 -8
View File
@@ -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
View File
@@ -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)
}
}
+49
View File
@@ -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) {}
}
+3 -1
View File
@@ -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)
}
+11 -9
View File
@@ -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
+31 -15
View File
@@ -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
}
}
+78 -65
View File
@@ -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)))
}
}
+40
View File
@@ -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]
}
}