Merge pull request #30 from MessageKit/v0.3.0

v0.3.0
This commit is contained in:
Steven Deutsch
2017-08-09 21:54:53 -05:00
committed by GitHub
32 changed files with 861 additions and 474 deletions
+4 -14
View File
@@ -7,24 +7,14 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa
## Upcoming release
----------------
## [Prerelease] 0.3.0
This release closes the [0.3 milestone](https://github.com/MessageKit/MessageKit/milestone/3?closed=1).
## [Prerelease] 0.2.0
This release closes the [0.2 milestone](https://github.com/MessageKit/MessageKit/milestone/2?closed=1).
### API Breaking
- `MessagesDataSource` & `MessagesDisplayDataSource` collectionView params are now typed as `MessagesCollectionView`.
### Enahancements
- Resizing `UITextView` in `MessageInputBar`.
- Adds basic support for `MessageHeaderView` and `MessageFooterView`.
- Adds `MessageCellDelegate` to handle tap events on message container or avatar.
### Bugfixes
- Fixes layout for `Landscape` orientation.
## [Prerelease] 0.1.0
This release closes the [0.1 milestone](https://github.com/MessageKit/MessageKit/milestone/1?closed=1).
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
37D3EAC41F390E5F00DD6A55 /* SampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D3EAC31F390E5F00DD6A55 /* SampleData.swift */; };
882B5E811CF7D53600B6E160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882B5E781CF7D53600B6E160 /* AppDelegate.swift */; };
882B5E821CF7D53600B6E160 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 882B5E791CF7D53600B6E160 /* Assets.xcassets */; };
882B5E831CF7D53600B6E160 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 882B5E7A1CF7D53600B6E160 /* LaunchScreen.storyboard */; };
@@ -74,6 +75,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
37D3EAC31F390E5F00DD6A55 /* SampleData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleData.swift; sourceTree = "<group>"; };
37F8BCD41F38F3A8003C12C2 /* Avatar.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = Avatar.playground; path = Playgrounds/Avatar.playground; sourceTree = "<group>"; };
882B5E331CF7D4B900B6E160 /* ChatExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
882B5E491CF7D4B900B6E160 /* ChatExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
882B5E541CF7D4B900B6E160 /* ChatExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -145,7 +148,9 @@
children = (
882B5E781CF7D53600B6E160 /* AppDelegate.swift */,
B0655A321F23E90800542A83 /* ConversationViewController.swift */,
37D3EAC31F390E5F00DD6A55 /* SampleData.swift */,
882B5E7E1CF7D53600B6E160 /* InboxViewController.swift */,
37F8BCD41F38F3A8003C12C2 /* Avatar.playground */,
B096438A1F288D47004D0129 /* MockMessage.swift */,
882B5E801CF7D53600B6E160 /* SettingsViewController.swift */,
882B5E791CF7D53600B6E160 /* Assets.xcassets */,
@@ -249,7 +254,7 @@
attributes = {
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0810;
ORGANIZATIONNAME = "Hexed Bits";
ORGANIZATIONNAME = MessageKit;
TargetAttributes = {
882B5E321CF7D4B900B6E160 = {
CreatedOnToolsVersion = 7.3.1;
@@ -342,6 +347,7 @@
buildActionMask = 2147483647;
files = (
882B5E871CF7D53600B6E160 /* SettingsViewController.swift in Sources */,
37D3EAC41F390E5F00DD6A55 /* SampleData.swift in Sources */,
B096438B1F288D47004D0129 /* MockMessage.swift in Sources */,
882B5E811CF7D53600B6E160 /* AppDelegate.swift in Sources */,
B0655A331F23E90800542A83 /* ConversationViewController.swift in Sources */,
+2 -2
View File
@@ -29,8 +29,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "NiceSelfi.jpg",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

+4 -91
View File
@@ -1,17 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="49e-Tb-3d3">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="qIf-zY-DwN">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Inbox-->
<scene sceneID="bfl-pY-VjE">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="6WU-8e-ICx" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tableViewController title="Inbox" clearsSelectionOnViewWillAppear="NO" id="Pev-jS-Pyn" customClass="InboxViewController" customModule="ChatExample" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="hyI-h1-cQJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
@@ -25,9 +26,6 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<connections>
<segue destination="liE-CN-1Q3" kind="show" id="4X0-1b-yom"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
@@ -35,30 +33,11 @@
<outlet property="delegate" destination="Pev-jS-Pyn" id="uuf-Ll-gIS"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Inbox" id="pM3-Dy-Tq2"/>
<navigationItem key="navigationItem" title="MessageKit" id="pM3-Dy-Tq2"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="6WU-8e-ICx" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1067" y="-252"/>
</scene>
<!--Conversation View Controller-->
<scene sceneID="DNz-5r-g7p">
<objects>
<viewController id="liE-CN-1Q3" customClass="ConversationViewController" customModule="ChatExample" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="X1Z-Rt-oge"/>
<viewControllerLayoutGuide type="bottom" id="jTg-1r-P9t"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="YUH-C1-4hJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="wuy-lF-2nR" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1817" y="-249"/>
</scene>
<!--Item-->
<scene sceneID="RIX-3V-AwK">
<objects>
@@ -76,71 +55,5 @@
</objects>
<point key="canvasLocation" x="338" y="-252"/>
</scene>
<!--Settings-->
<scene sceneID="pVX-a9-SMa">
<objects>
<tableViewController title="Settings" id="72O-av-jde" customClass="SettingsViewController" customModule="ChatExample" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="lpF-QO-eb8">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="cell" id="L7s-TD-5nE">
<rect key="frame" x="0.0" y="56" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="L7s-TD-5nE" id="3Kc-oF-DKn">
<rect key="frame" x="0.0" y="0.0" width="375" height="43"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="72O-av-jde" id="mYY-FH-eZJ"/>
<outlet property="delegate" destination="72O-av-jde" id="JHI-G3-2OK"/>
</connections>
</tableView>
<tabBarItem key="tabBarItem" title="Settings" id="LAD-r6-lZO"/>
<navigationItem key="navigationItem" title="Settings" id="CYW-K0-e3l"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="VzG-Xy-fQS" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1067" y="465"/>
</scene>
<!--Settings-->
<scene sceneID="5t5-pv-EFo">
<objects>
<navigationController title="Settings" id="HsT-8D-kIY" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Settings" id="nLg-o4-bd0"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="6gh-G8-7aR">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="72O-av-jde" kind="relationship" relationship="rootViewController" id="tOk-cH-lDJ"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="f8P-CH-n1o" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="338" y="465"/>
</scene>
<!--Tab Bar Controller-->
<scene sceneID="yl2-sM-qoP">
<objects>
<tabBarController id="49e-Tb-3d3" sceneMemberID="viewController">
<nil key="simulatedBottomBarMetrics"/>
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</tabBar>
<connections>
<segue destination="qIf-zY-DwN" kind="relationship" relationship="viewControllers" id="srD-y8-X25"/>
<segue destination="HsT-8D-kIY" kind="relationship" relationship="viewControllers" id="MuA-na-hKR"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-496" y="72"/>
</scene>
</scenes>
</document>
@@ -27,66 +27,17 @@ import MessageKit
class ConversationViewController: MessagesViewController {
var messages: [MessageType] = []
var messageList: [MockMessage] = []
override func viewDidLoad() {
super.viewDidLoad()
addSampleData()
messageList = SampleData().getMessages()
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messageCellDelegate = self
messagesCollectionView.messagesLayoutDelegate = self
messageInputBar.delegate = self
tabBarController?.tabBar.isHidden = true
}
func addSampleData() {
let sender1 = Sender(id: "123456", displayName: "Bobby")
let sender2 = Sender(id: "654321", displayName: "Steven")
let sender3 = Sender(id: "777999", displayName: "Omar")
let msg1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." +
"Pellentesque venenatis, ante et hendrerit rutrum" +
"Quam erat vehicula metus, et condimentum ante tellus augue."
let msg2 = "Cras efficitur bibendum mauris sed ultrices." +
"Phasellus tellus nisl, ullamcorper quis erat."
let msg3 = "Maecenas."
let msg4 = "Pellentesque venenatis, ante et hendrerit rutrum" +
"Quam erat vehicula metus, et condimentum ante tellus augue."
let msg5 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." +
"Pellentesque venenatis, ante et hendrerit rutrum" +
"Quam erat vehicula metus, et condimentum ante tellus augue."
messages.append(MockMessage(text: msg2, sender: sender2, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg4, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg5, sender: sender3, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg1, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg3, sender: sender1, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg3, sender: sender1, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg2, sender: sender2, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg2, sender: sender2, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg1, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg3, sender: sender1, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg2, sender: sender2, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg4, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg5, sender: sender3, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg4, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg5, sender: sender3, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg4, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg5, sender: sender3, id: NSUUID().uuidString))
messages.append(MockMessage(text: msg1, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg1, sender: currentSender(), id: NSUUID().uuidString))
messages.append(MockMessage(text: msg3, sender: sender1, id: NSUUID().uuidString))
}
}
// MARK: - MessagesDataSource
@@ -94,15 +45,15 @@ class ConversationViewController: MessagesViewController {
extension ConversationViewController: MessagesDataSource {
func currentSender() -> Sender {
return Sender(id: "123", displayName: "Steven")
return SampleData().getCurrentSender()
}
func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int {
return messages.count
return messageList.count
}
func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
return messages[indexPath.section]
return messageList[indexPath.section]
}
}
@@ -111,9 +62,8 @@ extension ConversationViewController: MessagesDataSource {
extension ConversationViewController: MessagesDisplayDataSource {
func avatarForMessage(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarView {
let image = isFromCurrentSender(message: message) ? #imageLiteral(resourceName: "Steve-Jobs") : #imageLiteral(resourceName: "Tim-Cook")
return AvatarView(image: image)
func avatarForMessage(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
return SampleData().getAvatarFor(sender: message.sender)
}
func headerForMessage(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? {
@@ -124,6 +74,18 @@ extension ConversationViewController: MessagesDisplayDataSource {
return messagesCollectionView.dequeueMessageFooterView(for: indexPath)
}
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let name = message.sender.displayName
return NSAttributedString(string: name, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption1)])
}
func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
let formatter = DateFormatter()
formatter.dateStyle = .medium
let dateString = formatter.string(from: message.sentDate)
return NSAttributedString(string: dateString, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption2)])
}
}
// MARK: - MessagesLayoutDelegate
@@ -159,13 +121,9 @@ extension ConversationViewController: MessageCellDelegate {
extension ConversationViewController: MessageInputBarDelegate {
func sendButtonPressed(sender: UIButton, textView: UITextView) {
guard let message = textView.text else { return }
messages.append(MockMessage(text: message, sender: currentSender(), id: NSUUID().uuidString))
messageList.append(MockMessage(text: message, sender: currentSender(), messageId: UUID().uuidString))
messagesCollectionView.reloadData()
}
}
+18 -3
View File
@@ -27,18 +27,33 @@ import MessageKit
final class InboxViewController: UITableViewController {
let cells = ["Test", "Settings"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
return cells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell()
cell.textLabel?.text = "Test"
cell.textLabel?.text = cells[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = cells[indexPath.row]
switch cell {
case "Test":
navigationController?.pushViewController(ConversationViewController(), animated: true)
case "Settings":
navigationController?.pushViewController(SettingsViewController(), animated: true)
default:
assertionFailure("You need to impliment the action for this cell: \(cell)")
return
}
}
}
+2 -2
View File
@@ -32,10 +32,10 @@ struct MockMessage: MessageType {
var sentDate: Date
var data: MessageData
init(text: String, sender: Sender, id: String) {
init(text: String, sender: Sender, messageId: String) {
data = .text(text)
self.sender = sender
self.messageId = id
self.messageId = messageId
self.sentDate = Date()
}
@@ -0,0 +1,29 @@
import UIKit
import MessageKit
import PlaygroundSupport
//: Discover what is possible with the Avatar Class
//Get an image
let testImage = #imageLiteral(resourceName: "NiceSelfi.jpg")
var avatarView = AvatarView()
//: Uncomment any line to see how it changes the `Avatar`. Change the parameters and see the effects.
//: By default its a circlular avatar with a gray background and initals of "?"
//: Create an avatar object and set it for the view.
//var avatarObject = Avatar(image: testImage)
//avatarView.set(avatar: avatarObject)
//: If you don't have a picture for the user you can pass in there initals instead.
//avatarObject = Avatar(initals: "DL")
//avatarView.set(avatar: avatarObject)
//: Want rounded squares instead of circles just adjust the radius with the method .setCorner(radius: CGFLoat)`.
//avatarView.setCorner(radius: 5)
//: Everything has a default so if you dont want to set it then you dont have to.
//Helper method.
PlaygroundPage.current.liveView = avatarView
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios'>
<playground version='5.0' target-platform='ios' display-mode='rendered'>
<timeline fileName='timeline.xctimeline'/>
</playground>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
+64
View File
@@ -0,0 +1,64 @@
/*
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 MessageKit
struct SampleData {
let Dan = Sender(id: "123456", displayName: "Dan Leonard")
let Steven = Sender(id: "654321", displayName: "Steven")
let Jobs = Sender(id: "000001", displayName: "Steve Jobs")
let Cook = Sender(id: "656361", displayName: "Tim Cook")
func getMessages() -> [MockMessage] {
let msg1 = MockMessage(text: "Check out this awesome UI library for Chat", sender: Dan, messageId: UUID().uuidString)
let msg2 = MockMessage(text: "This is insane.", sender: Steven, messageId: UUID().uuidString)
let msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Cook, messageId: UUID().uuidString)
let msg4 = MockMessage(text: "My favorite things in life dont cost any money. Its really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString)
let msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Cook, messageId: UUID().uuidString)
let msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out 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)
return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8]
}
func getCurrentSender() -> Sender {
return Dan
}
func getAvatarFor(sender: Sender) -> Avatar {
switch sender {
case Dan:
return Avatar(image: #imageLiteral(resourceName: "Dan-Leonard"), initals: "DL")
case Steven:
return Avatar(initals: "S")
case Jobs:
return Avatar(image: #imageLiteral(resourceName: "Steve-Jobs"), initals: "SJ")
case Cook:
return Avatar(image: #imageLiteral(resourceName: "Tim-Cook"))
default:
return Avatar()
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'MessageKit'
s.version = '0.2.0'
s.version = '0.3.0'
s.license = { :type => "MIT", :file => "LICENSE.md" }
s.summary = 'An elegant messages UI library for iOS.'
+17 -3
View File
@@ -7,8 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
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 */; };
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 */; };
88916B2D1CF0DF2F00469F91 /* MessageKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88916B221CF0DF2F00469F91 /* MessageKit.framework */; };
@@ -28,6 +30,8 @@
B074EE931F35587100ABB8C8 /* MessageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */; };
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 */; };
@@ -44,9 +48,10 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
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>"; };
372F6AED1F36C1C100B57FBD /* AvatarView.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = AvatarView.playground; sourceTree = "<group>"; };
372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarViewTests.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>"; };
88916B221CF0DF2F00469F91 /* MessageKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -69,6 +74,8 @@
B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHeaderView.swift; sourceTree = "<group>"; };
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>"; };
@@ -140,6 +147,7 @@
children = (
B09643851F286C9E004D0129 /* String+Extensions.swift */,
B096438F1F289142004D0129 /* UIColor+Extensions.swift */,
B074EEA91F3BE8F300ABB8C8 /* NSAttributedString+Extensions.swift */,
);
name = Extensions;
sourceTree = "<group>";
@@ -149,6 +157,7 @@
children = (
B0655A291F23D77200542A83 /* Sender.swift */,
B0655A2B1F23D81600542A83 /* MessageData.swift */,
37C936971F38F6AC00853DF2 /* Avatar.swift */,
B0655A271F23D71400542A83 /* MessageDirection.swift */,
);
name = Models;
@@ -160,10 +169,11 @@
B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */,
B0655A4C1F244C0600542A83 /* MessageCollectionViewCell.swift */,
372F6AEA1F36C15600B57FBD /* AvatarView.swift */,
372F6AED1F36C1C100B57FBD /* AvatarView.playground */,
B0655A371F23EE8B00542A83 /* MessageInputBar.swift */,
171D5AB81F36712B0053DF69 /* InputTextView.swift */,
B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */,
B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */,
B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */,
);
name = Views;
sourceTree = "<group>";
@@ -266,7 +276,7 @@
attributes = {
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0810;
ORGANIZATIONNAME = "Hexed Bits";
ORGANIZATIONNAME = MessageKit;
TargetAttributes = {
88916B211CF0DF2F00469F91 = {
CreatedOnToolsVersion = 7.3.1;
@@ -333,6 +343,8 @@
isa = PBXSourcesBuildPhase;
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 */,
@@ -348,9 +360,11 @@
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 */,
88916B471CF0DFE600469F91 /* MessageType.swift in Sources */,
B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */,
37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */,
B096438E1F2890FB004D0129 /* MessagesDisplayDataSource.swift in Sources */,
B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */,
);
+20
View File
@@ -0,0 +1,20 @@
//
// Avatar.swift
// MessageKit
//
// Created by Dan Leonard on 8/7/17.
// Copyright © 2017 MessageKit. All rights reserved.
//
import Foundation
public struct Avatar {
public let image: UIImage?
public var initals: String = "?"
public init(image: UIImage? = nil, initals: String = "?") {
self.image = image
self.initals = initals
}
}
@@ -1,37 +0,0 @@
import UIKit
import MessageKit
import PlaygroundSupport
//: Discover what is possible with the Avatar Class
//Get an image
let testImage = #imageLiteral(resourceName: "NiceSelfi.jpg")
let view = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 200))
view.backgroundColor = UIColor.white
//: Uncomment any line to see how it changes the `Avatar`.
//: By default its a circlular avatar with a gray background and initals of ?
let avatar = AvatarView()
//: Configure any one of the initilization parameters and delete the ones you dont want to set.
//let avatar = AvatarView(size: 50, image: testImage, highlightedImage: testImage, initals: "PL", cornerRounding: 9)
//: Throw in just an image.
//let avatar = AvatarView(image: testImage)
//: Dont have an image just add the users initals
//let avatar = AvatarView(initals: "PL")
//: Want rounded squares instead of circles just change the `cornderRounding`.
//let avatar = AvatarView(image: testImage, cornerRounding: 9)
//:Change its size
//let avatar = AvatarView(size: 5)
//let avatar = AvatarView(size: 100)
//: Everything has a default so if you dont want to set it then you dont have to.
//Helper method.
PlaygroundPage.current.liveView = avatar
+32 -42
View File
@@ -26,9 +26,8 @@ import Foundation
open class AvatarView: UIView {
// MARK: - Properties
internal var initalsLabel = UILabel()
internal var avatar: Avatar = Avatar()
internal var imageView = UIImageView()
internal var initals: String = "?"
// MARK: - initializers
override init(frame: CGRect) {
@@ -36,25 +35,31 @@ open class AvatarView: UIView {
prepareView()
}
convenience public init(size: CGFloat = 30, image: UIImage? = nil, highlightedImage: UIImage? = nil, initals inInitals: String = "?", cornerRounding: CGFloat? = nil) {
let frame = CGRect(x: 0, y: 0, width: size, height: size)
self.init(frame: frame)
setCorner(radius: cornerRounding)
setBackground(color: UIColor.gray)
imageView.image = image
imageView.highlightedImage = highlightedImage
initals = inInitals
prepareView()
}
convenience public init() {
let frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.init(frame: frame)
setBackground(color: UIColor.gray)
setCorner(radius: nil)
prepareView()
}
func getImageFrom(initals: String, withColor color: UIColor = UIColor.white, fontSize: CGFloat = 14) -> UIImage {
_ = UIGraphicsBeginImageContext(CGSize(width: 30, height: 30))
let context = UIGraphicsGetCurrentContext()!
//// Text Drawing
let textRect = CGRect(x: 5, y: 6, width: 20, height: 20)
let textStyle = NSMutableParagraphStyle()
textStyle.alignment = .center
let textFontAttributes = [NSFontAttributeName: UIFont.systemFont(ofSize: fontSize), NSForegroundColorAttributeName: color, NSParagraphStyleAttributeName: textStyle]
let textTextHeight: CGFloat = initals.boundingRect(with: CGSize(width: textRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: textFontAttributes, context: nil).height
context.saveGState()
context.clip(to: textRect)
initals.draw(in: CGRect(x: textRect.minX, y: textRect.minY + (textRect.height - textTextHeight) / 2, width: textRect.width, height: textTextHeight), withAttributes: textFontAttributes)
context.restoreGState()
guard let renderedImage = UIGraphicsGetImageFromCurrentImageContext() else { assertionFailure("Could not create image from context"); return UIImage()}
return renderedImage
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@@ -62,48 +67,27 @@ open class AvatarView: UIView {
// MARK: - internal methods
internal func prepareView() {
prepareInitalsLabel()
prepareImageView()
imageView.isHidden = imageView.image == nil
}
internal func prepareInitalsLabel() {
initalsLabel.text = initals
initalsLabel.textAlignment = .center
setInitalsFont()
addSubview(initalsLabel)
initalsLabel.center = center
initalsLabel.frame = frame
}
internal func prepareImageView() {
setBackground(color: UIColor.gray)
contentMode = .scaleAspectFill
layer.masksToBounds = true
clipsToBounds = true
addSubview(imageView)
imageView.contentMode = .scaleAspectFill
imageView.frame = frame
imageView.image = avatar.image ?? getImageFrom(initals: avatar.initals)
setCorner(radius: nil)
}
// MARK: - Open methods
// MARK: - Open setters
open func set(image: UIImage) {
imageView.image = image
}
open func setInitalsFont(size: CGFloat = 16, color: UIColor = .white) {
initalsLabel.font = UIFont.systemFont(ofSize: size)
initalsLabel.textColor = color
open func set(avatar: Avatar) {
imageView.image = avatar.image ?? getImageFrom(initals: avatar.initals)
}
open func setBackground(color: UIColor) {
backgroundColor = color
}
open func getImage() -> UIImage? {
return imageView.image
}
open func setCorner(radius: CGFloat?) {
guard let radius = radius else {
//if corner radius not set default to Circle
@@ -112,4 +96,10 @@ open class AvatarView: UIView {
}
layer.cornerRadius = radius
}
// MARK: - Open getters
open func getImage() -> UIImage? {
return imageView.image
}
}
+119
View File
@@ -0,0 +1,119 @@
/*
MIT License
Copyright (c) 2017 MessageKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import UIKit
open class InputTextView: UITextView {
// MARK: - Properties
open var placeholder: NSString? {
didSet {
guard placeholder != oldValue else { return }
setNeedsDisplay()
}
}
open var placeholderTextColor: UIColor = .lightGray {
didSet {
guard placeholderTextColor != oldValue else { return }
setNeedsDisplay()
}
}
open var placeholderInsets: UIEdgeInsets = UIEdgeInsets(top: 7,
left: 5,
bottom: 7,
right: 5) {
didSet {
guard placeholderInsets != oldValue else { return }
setNeedsDisplay()
}
}
private var isPlaceholderVisibile = false
// MARK: - Initializers
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
addObservers()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addObservers()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Methods
override open func draw(_ rect: CGRect) {
super.draw(rect)
guard text.isEmpty, let placeholder = placeholder else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = textAlignment
var attributes: [String: Any] = [
NSForegroundColorAttributeName: placeholderTextColor,
NSParagraphStyleAttributeName: paragraphStyle
]
if let font = font {
attributes[NSFontAttributeName] = font
}
placeholder.draw(in: UIEdgeInsetsInsetRect(rect, placeholderInsets),
withAttributes: attributes)
isPlaceholderVisibile = true
}
fileprivate func addObservers() {
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: Notification.Name.UITextViewTextDidChange,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.orientationChanged(notification:)),
name: Notification.Name.UIDeviceOrientationDidChange,
object: nil)
}
func textDidChange(notification: Notification) {
guard text.isEmpty || isPlaceholderVisibile else { return }
setNeedsDisplay()
isPlaceholderVisibile = false
}
func orientationChanged(notification: Notification) {
setNeedsDisplay()
}
}
+72 -49
View File
@@ -29,31 +29,19 @@ open class MessageCollectionViewCell: UICollectionViewCell {
// MARK: - Properties
open let messageContainerView: UIView = {
let messageContainerView = UIView()
messageContainerView.layer.cornerRadius = 12.0
messageContainerView.layer.masksToBounds = true
return messageContainerView
}()
open let avatarImageView: UIImageView = {
open var avatarView: AvatarView = AvatarView()
let avatarImageView = UIImageView()
avatarImageView.contentMode = .scaleAspectFill
avatarImageView.backgroundColor = .lightGray
avatarImageView.layer.masksToBounds = true
avatarImageView.clipsToBounds = true
return avatarImageView
}()
open var cellTopLabel: MessageLabel = MessageLabel()
open let messageLabel: UILabel = {
open var messageLabel: MessageLabel = MessageLabel()
let messageLabel = UILabel()
messageLabel.numberOfLines = 0
messageLabel.backgroundColor = .clear
messageLabel.isOpaque = false
return messageLabel
}()
open var cellBottomLabel: MessageLabel = MessageLabel()
open weak var delegate: MessageCellDelegate?
@@ -74,9 +62,11 @@ open class MessageCollectionViewCell: UICollectionViewCell {
private func setupSubviews() {
contentView.addSubview(cellTopLabel)
contentView.addSubview(messageContainerView)
contentView.addSubview(avatarImageView)
messageContainerView.addSubview(messageLabel)
contentView.addSubview(avatarView)
contentView.addSubview(cellBottomLabel)
}
@@ -85,54 +75,85 @@ open class MessageCollectionViewCell: UICollectionViewCell {
guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else { return }
messageLabel.font = attributes.messageFont
cellTopLabel.frame = cellTopLabelFrame(for: attributes)
cellTopLabel.textInsets = attributes.cellTopLabelInsets
setAvatarFrameFor(attributes: attributes)
setMessageContainerFrameFor(attributes: attributes)
setMessageLabelFor(attributes: attributes)
messageContainerView.frame = messageContainerFrame(for: attributes)
messageLabel.frame = CGRect(origin: .zero, size: attributes.messageContainerSize)
messageLabel.textInsets = attributes.messageLabelInsets
}
avatarView.frame = avatarViewFrame(for: attributes)
private func setMessageContainerFrameFor(attributes: MessagesCollectionViewLayoutAttributes) {
cellBottomLabel.frame = cellBottomLabelFrame(for: attributes)
cellBottomLabel.textInsets = attributes.cellBottomLabelInsets
switch attributes.direction {
case .incoming:
let x = attributes.avatarSize.width + attributes.avatarContainerSpacing
messageContainerView.frame = CGRect(x: x,
y: 0,
width: attributes.messageContainerSize.width,
height: attributes.messageContainerSize.height)
cellTopLabel.textAlignment = .left
cellBottomLabel.textAlignment = .right
case .outgoing:
let x = contentView.frame.width - attributes.avatarSize.width - attributes.avatarContainerSpacing - attributes.messageContainerSize.width
messageContainerView.frame = CGRect(x: x,
y: 0,
width: attributes.messageContainerSize.width,
height: attributes.messageContainerSize.height)
cellTopLabel.textAlignment = .right
cellBottomLabel.textAlignment = .left
}
}
private func setAvatarFrameFor(attributes: MessagesCollectionViewLayoutAttributes) {
func cellTopLabelFrame(for attributes: MessagesCollectionViewLayoutAttributes) -> CGRect {
switch attributes.direction {
case .incoming:
let y = frame.height - attributes.avatarSize.height - attributes.avatarBottomSpacing
avatarImageView.frame = CGRect(x: 0, y: y, width: attributes.avatarSize.width, height: attributes.avatarSize.height)
case .outgoing:
let y = frame.height - attributes.avatarSize.height - attributes.avatarBottomSpacing
let x = contentView.frame.width - attributes.avatarSize.width
avatarImageView.frame = CGRect(x: x, y: y, width: attributes.avatarSize.width, height: attributes.avatarSize.height)
var origin: CGPoint = .zero
if attributes.topLabelPinnedUnderMessage {
origin = CGPoint(x: attributes.avatarSize.width + attributes.avatarMessagePadding, y: 0)
}
avatarImageView.layer.cornerRadius = avatarImageView.frame.width / 2
return CGRect(origin: origin, size: attributes.cellTopLabelSize)
}
func cellBottomLabelFrame(for attributes: MessagesCollectionViewLayoutAttributes) -> CGRect {
var origin: CGPoint = CGPoint(x: 0, y: contentView.frame.height - attributes.cellBottomLabelSize.height)
if attributes.bottomLabelPinnedUnderMessage {
origin.x = attributes.avatarSize.width + attributes.avatarMessagePadding
}
return CGRect(origin: origin, size: attributes.cellBottomLabelSize)
}
func messageContainerFrame(for attributes: MessagesCollectionViewLayoutAttributes) -> CGRect {
var origin: CGPoint = .zero
let yPosition = attributes.cellTopLabelSize.height
switch attributes.direction {
case .outgoing:
let xPosition = contentView.frame.width - attributes.avatarSize.width - attributes.avatarMessagePadding - attributes.messageContainerSize.width
origin = CGPoint(x: xPosition, y: yPosition)
case .incoming:
let xPosition = attributes.avatarSize.width + attributes.avatarMessagePadding
origin = CGPoint(x: xPosition, y: yPosition)
}
return CGRect(origin: origin, size: attributes.messageContainerSize)
}
private func setMessageLabelFor(attributes: MessagesCollectionViewLayoutAttributes) {
func avatarViewFrame(for attributes: MessagesCollectionViewLayoutAttributes) -> CGRect {
let frame = CGRect(x: 0, y: 0, width: attributes.messageContainerSize.width, height: attributes.messageContainerSize.height)
let insetFrame = UIEdgeInsetsInsetRect(frame, attributes.messageContainerInsets)
messageLabel.frame = insetFrame
var origin: CGPoint = .zero
let yPosition = contentView.frame.height - attributes.avatarSize.height - attributes.avatarBottomPadding - attributes.cellBottomLabelSize.height
switch attributes.direction {
case .outgoing:
let xPosition = contentView.frame.width - attributes.avatarSize.width
origin = CGPoint(x: xPosition, y: yPosition)
case .incoming:
origin = CGPoint(x: 0, y: yPosition)
}
return CGRect(origin: origin, size: attributes.avatarSize)
}
@@ -141,6 +162,8 @@ open class MessageCollectionViewCell: UICollectionViewCell {
switch message.data {
case .text(let text):
messageLabel.text = text
case .attributedText(let text):
messageLabel.attributedText = text
}
}
@@ -148,8 +171,8 @@ open class MessageCollectionViewCell: UICollectionViewCell {
func setupGestureRecognizers() {
let avatarTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapAvatar))
avatarImageView.addGestureRecognizer(avatarTapGesture)
avatarImageView.isUserInteractionEnabled = true
avatarView.addGestureRecognizer(avatarTapGesture)
avatarView.isUserInteractionEnabled = true
let messageTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapMessage))
messageContainerView.addGestureRecognizer(messageTapGesture)
+1 -2
View File
@@ -28,11 +28,10 @@ import Foundation
public enum MessageData {
case text(String)
case attributedText(NSAttributedString)
// MARK: - Not supported yet
// case attributedText(NSAttributedString)
//
// case audio(Data)
//
// case location(CLLocation)
+13 -6
View File
@@ -30,10 +30,10 @@ open class MessageInputBar: UIView, UITextViewDelegate {
open let inputTextView: UITextView = {
let inputTextView = UITextView(frame: .zero)
let inputTextView = InputTextView(frame: .zero)
inputTextView.font = UIFont.preferredFont(forTextStyle: .body)
inputTextView.text = "New Message"
inputTextView.textColor = .lightGray
inputTextView.textColor = .black
inputTextView.placeholder = "New Message"
inputTextView.backgroundColor = .white
inputTextView.layer.borderColor = UIColor.lightGray.cgColor
inputTextView.layer.borderWidth = 1.0
@@ -47,7 +47,12 @@ open class MessageInputBar: UIView, UITextViewDelegate {
let sendButton = UIButton()
sendButton.setTitle("Send", for: .normal)
sendButton.setTitleColor(.lightGray, for: .normal)
sendButton.setTitleColor(.sendButtonBlue, for: .normal)
sendButton.setTitleColor(UIColor.sendButtonBlue.withAlphaComponent(0.3), for: .highlighted)
sendButton.setTitleColor(.lightGray, for: .disabled)
sendButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .headline)
sendButton.isEnabled = false
return sendButton
}()
@@ -91,13 +96,15 @@ open class MessageInputBar: UIView, UITextViewDelegate {
}
public func textViewDidChange(_ textView: UITextView) {
let trimmedText = textView.text.trimmingCharacters(in: .whitespacesAndNewlines)
sendButton.isEnabled = !trimmedText.isEmpty
invalidateIntrinsicContentSize()
}
override open var intrinsicContentSize: CGSize {
let sizeToFit = inputTextView.sizeThatFits(CGSize(width: inputTextView.bounds.width, height: .greatestFiniteMagnitude))
let heightToFit = sizeToFit.height.rounded()
return CGSize(width: bounds.width, height: heightToFit + 8)
let heightToFit = sizeToFit.height.rounded() + 8 // constraint padding
return CGSize(width: bounds.width, height: heightToFit)
}
private func setupSubviews() {
+53
View File
@@ -0,0 +1,53 @@
/*
MIT License
Copyright (c) 2017 MessageKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import UIKit
open class MessageLabel: UILabel {
// MARK: - Properties
open var textInsets: UIEdgeInsets = .zero {
didSet {
guard textInsets != oldValue else { return }
setNeedsDisplay()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
numberOfLines = 0
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Methods
open override func draw(_ rect: CGRect) {
super.drawText(in: UIEdgeInsetsInsetRect(rect, textInsets))
}
}
+179 -63
View File
@@ -28,17 +28,25 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
// MARK: - Properties
open var messageFont: UIFont
open var messageLabelFont: UIFont
open var messageLabelInsets: UIEdgeInsets
open var messageToViewEdgePadding: CGFloat
open var cellTopLabelInsets: UIEdgeInsets
open var topLabelPinnedUnderMessage: Bool
open var cellBottomLabelInsets: UIEdgeInsets
open var bottomLabelPinnedUnderMessage: Bool
open var incomingAvatarSize: CGSize
open var outgoingAvatarSize: CGSize
open var messageContainerInsets: UIEdgeInsets
fileprivate var avatarBottomPadding: CGFloat = 2
fileprivate var avatarMessagePadding: CGFloat = 4
fileprivate let avatarBottomSpacing: CGFloat = 4
fileprivate let avatarContainerSpacing: CGFloat = 4
fileprivate var messagesCollectionView: MessagesCollectionView? {
return collectionView as? MessagesCollectionView
}
fileprivate var itemWidth: CGFloat {
guard let collectionView = collectionView else { return 0 }
@@ -52,11 +60,22 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
// MARK: - Initializers
override public init() {
messageFont = UIFont.preferredFont(forTextStyle: .body)
messageLabelFont = UIFont.preferredFont(forTextStyle: .body)
messageLabelInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 14)
messageToViewEdgePadding = 30.0
cellTopLabelInsets = .zero
topLabelPinnedUnderMessage = true
cellBottomLabelInsets = .zero
bottomLabelPinnedUnderMessage = true
incomingAvatarSize = CGSize(width: 30, height: 30)
outgoingAvatarSize = CGSize(width: 30, height: 30)
messageContainerInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 14)
super.init()
sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
}
@@ -93,22 +112,31 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
private func configure(attributes: MessagesCollectionViewLayoutAttributes) {
guard let collectionView = collectionView as? MessagesCollectionView, let dataSource = collectionView.messagesDataSource else { return }
guard let messagesCollectionView = messagesCollectionView else { return }
guard let dataSource = messagesCollectionView.messagesDataSource else { return }
let indexPath = attributes.indexPath
let message = dataSource.messageForItem(at: indexPath, in: collectionView)
let direction: MessageDirection = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming
let avatarSize = avatarSizeFor(message: message)
let messageContainerSize = containerSizeFor(message: message)
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
attributes.direction = direction
attributes.messageFont = messageFont
attributes.messageContainerSize = messageContainerSize
attributes.messageContainerInsets = messageContainerInsets
attributes.avatarSize = avatarSize
attributes.avatarBottomSpacing = avatarBottomSpacing
attributes.avatarContainerSpacing = avatarContainerSpacing
attributes.messageContainerSize = messageContainerSize(for: message, at: indexPath)
attributes.messageLabelFont = messageLabelFont
attributes.messageLabelInsets = messageLabelInsets
attributes.messageToViewEdgePadding = messageToViewEdgePadding
attributes.cellTopLabelSize = cellTopLabelSize(for: message, at: indexPath)
attributes.cellTopLabelInsets = cellTopLabelInsets
attributes.topLabelPinnedUnderMessage = topLabelPinnedUnderMessage
attributes.cellBottomLabelSize = cellBottomLabelSize(for: message, at: indexPath)
attributes.cellBottomLabelInsets = cellBottomLabelInsets
attributes.bottomLabelPinnedUnderMessage = bottomLabelPinnedUnderMessage
attributes.avatarSize = avatarSize(for: message)
attributes.avatarBottomPadding = avatarBottomPadding
attributes.avatarMessagePadding = avatarMessagePadding
attributes.direction = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming
}
@@ -129,91 +157,179 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
extension MessagesCollectionViewFlowLayout {
func avatarSizeFor(message: MessageType) -> CGSize {
// MARK: - Size Calculations
guard let collectionView = collectionView as? MessagesCollectionView, let dataSource = collectionView.messagesDataSource else { return .zero }
func avatarSize(for message: MessageType) -> CGSize {
return dataSource.isFromCurrentSender(message: message) ? outgoingAvatarSize : incomingAvatarSize
guard let messagesCollectionView = messagesCollectionView else { return .zero }
guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .zero }
let isOutgoingMessage = messagesDataSource.isFromCurrentSender(message: message)
return isOutgoingMessage ? outgoingAvatarSize : incomingAvatarSize
}
func messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
let messageWidth = messageContainerWidth(for: message, at: indexPath)
let messageHeight = messageContainerHeight(for: message, at: indexPath)
return CGSize(width: messageWidth, height: messageHeight)
}
func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
let topLabelWidth = cellTopLabelWidth(for: message)
let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
return CGSize(width: topLabelWidth, height: topLabelHeight)
}
func minimumCellHeightFor(message: MessageType) -> CGFloat {
func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
guard let collectionView = collectionView as? MessagesCollectionView, let dataSource = collectionView.messagesDataSource else { return 0 }
let bottomLabelWidth = cellBottomLabelWidth(for: message)
let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath)
let messageDirection: MessageDirection = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming
return CGSize(width: bottomLabelWidth, height: bottomLabelHeight)
switch messageDirection {
case .incoming:
return incomingAvatarSize.height + avatarBottomSpacing
case .outgoing:
return outgoingAvatarSize.height + avatarBottomSpacing
}
// MARK: - Width Calculations
func cellTopLabelWidth(for message: MessageType) -> CGFloat {
if topLabelPinnedUnderMessage {
return itemWidth - avatarSize(for: message).width - avatarMessagePadding - messageToViewEdgePadding
} else {
return itemWidth
}
}
func cellBottomLabelWidth(for message: MessageType) -> CGFloat {
if bottomLabelPinnedUnderMessage {
return itemWidth - avatarSize(for: message).width - avatarMessagePadding - messageToViewEdgePadding
} else {
return itemWidth
}
}
func availableWidthForMessageContainer(considering message: MessageType) -> CGFloat {
let avatarWidth = avatarSize(for: message).width
let avatarWidthPlusPadding = avatarWidth == 0 ? 0 : avatarWidth + avatarMessagePadding
let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right
let availableWidth = itemWidth - avatarWidthPlusPadding - horizontalMessageInsets - messageToViewEdgePadding
return availableWidth
}
// MARK: - View Height Calculations
func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
guard let messagesCollectionView = messagesCollectionView else { return 0 }
guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return 0 }
guard let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 }
let availableWidth = cellTopLabelWidth(for: message)
let estimatedHeight = topLabelText.height(considering: availableWidth)
return estimatedHeight.rounded(.up)
}
func containerHeightForMessage(message: MessageType) -> CGFloat {
func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
let avatarSize = avatarSizeFor(message: message)
let insets = messageContainerInsets.left + messageContainerInsets.right
let availableWidth = itemWidth - avatarSize.width - avatarContainerSpacing - insets
guard let messagesCollectionView = messagesCollectionView else { return 0 }
guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return 0 }
guard let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 }
let availableWidth = cellBottomLabelWidth(for: message)
let estimatedHeight = bottomLabelText.height(considering: availableWidth)
return estimatedHeight.rounded(.up)
}
func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
let size = avatarSize(for: message)
let avatarHeightPlusBottomPadding = size.height == 0 ? 0 : size.height + avatarBottomPadding
let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath)
let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
let minimumHeight = topLabelHeight + avatarHeightPlusBottomPadding + bottomLabelHeight
return minimumHeight
}
func messageContainerHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
let availableWidth = availableWidthForMessageContainer(considering: message)
let verticalMessageInsets = messageLabelInsets.top + messageLabelInsets.bottom
var estimatedHeight: CGFloat = 0
// This is a switch because support for more messages are to come
switch message.data {
case .text(let text):
let estimatedHeight = text.height(considering: availableWidth, and: messageFont)
let insets = messageContainerInsets.top + messageContainerInsets.bottom
return estimatedHeight.rounded(.up) + insets //+ 1
estimatedHeight = text.height(considering: availableWidth, and: messageLabelFont)
case .attributedText(let text):
estimatedHeight = text.height(considering: availableWidth)
}
let finalHeight = estimatedHeight.rounded(.up) + verticalMessageInsets
return finalHeight
}
func containerWidthForMessage(message: MessageType) -> CGFloat {
// MARK: - View Width Calculations
let containerHeight = containerHeightForMessage(message: message)
func messageContainerWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
let avatarSize = avatarSizeFor(message: message)
let insets = messageContainerInsets.left + messageContainerInsets.right
let availableWidth = itemWidth - avatarSize.width - avatarContainerSpacing - insets
let containerHeight = messageContainerHeight(for: message, at: indexPath)
let availableWidth = availableWidthForMessageContainer(considering: message)
let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right
var estimatedWidth: CGFloat = 0
// This is a switch because support for more messages are to come
switch message.data {
case .text(let text):
let estimatedWidth = text.width(considering: containerHeight, and: messageFont).rounded(.up)
let insets = messageContainerInsets.left + messageContainerInsets.right
let finalWidth = estimatedWidth > availableWidth ? availableWidth : estimatedWidth
return finalWidth + insets
estimatedWidth = text.width(considering: containerHeight, and: messageLabelFont)
case .attributedText(let text):
estimatedWidth = text.width(considering: containerHeight)
}
}
let widthToUse = estimatedWidth.rounded(.up) > availableWidth ? availableWidth : estimatedWidth
func estimatedCellHeightForMessage(message: MessageType) -> CGFloat {
let finalWidth = widthToUse + horizontalMessageInsets
let messageContainerHeight = containerHeightForMessage(message: message)
return messageContainerHeight
return finalWidth
}
func containerSizeFor(message: MessageType) -> CGSize {
// MARK: - Cell Size Calculations
let containerHeight = containerHeightForMessage(message: message)
let containerWidth = containerWidthForMessage(message: message)
func estimatedCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
return CGSize(width: containerWidth, height: containerHeight)
let containerHeight = messageContainerHeight(for: message, at: indexPath)
let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath)
let bottomLabelheight = cellBottomLabelHeight(for: message, at: indexPath)
return topLabelHeight + containerHeight + bottomLabelheight
}
func sizeForItem(at indexPath: IndexPath) -> CGSize {
guard let collectionView = collectionView as? MessagesCollectionView, let dataSource = collectionView.messagesDataSource else { return .zero }
guard let messagesCollectionView = messagesCollectionView else { return .zero }
guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
let message = dataSource.messageForItem(at: indexPath, in: collectionView)
let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
let minHeight = minimumCellHeightFor(message: message)
let estimatedHeight = estimatedCellHeightForMessage(message: message)
let actualHeight = estimatedHeight < minHeight ? minHeight : estimatedHeight
let minimumHeight = minimumCellHeight(for: message, at: indexPath)
let estimatedHeight = estimatedCellHeight(for: message, at: indexPath)
let finalHeight = estimatedHeight < minimumHeight ? minimumHeight : estimatedHeight
return CGSize(width: itemWidth, height: actualHeight)
return CGSize(width: itemWidth, height: finalHeight)
}
@@ -28,32 +28,44 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib
// MARK: - Properties
var direction: MessageDirection = .outgoing
var messageFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
var messageContainerSize: CGSize = .zero
var messageLabelFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
var messageLabelInsets: UIEdgeInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 14)
var messageToViewEdgePadding: CGFloat = 30.0
var messageContainerInsets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
var cellTopLabelSize: CGSize = .zero
var cellTopLabelInsets: UIEdgeInsets = .zero
var topLabelPinnedUnderMessage = true
var cellBottomLabelSize: CGSize = .zero
var cellBottomLabelInsets: UIEdgeInsets = .zero
var bottomLabelPinnedUnderMessage = true
var avatarSize: CGSize = CGSize(width: 30, height: 30)
var avatarBottomPadding: CGFloat = 4.0
var avatarMessagePadding: CGFloat = 4.0
var avatarBottomSpacing: CGFloat = 4
var avatarContainerSpacing: CGFloat = 4
var direction: MessageDirection = .incoming
// MARK: - Methods
override func copy(with zone: NSZone? = nil) -> Any {
// swiftlint:disable force_cast
let copy = super.copy(with: zone) as! MessagesCollectionViewLayoutAttributes
copy.direction = direction
copy.messageFont = messageFont
copy.messageContainerSize = messageContainerSize
copy.messageLabelFont = messageLabelFont
copy.messageLabelInsets = messageLabelInsets
copy.messageToViewEdgePadding = messageToViewEdgePadding
copy.cellTopLabelSize = cellTopLabelSize
copy.cellTopLabelInsets = cellTopLabelInsets
copy.topLabelPinnedUnderMessage = topLabelPinnedUnderMessage
copy.cellBottomLabelSize = cellBottomLabelSize
copy.cellBottomLabelInsets = cellBottomLabelInsets
copy.bottomLabelPinnedUnderMessage = bottomLabelPinnedUnderMessage
copy.avatarSize = avatarSize
copy.messageContainerInsets = messageContainerInsets
copy.avatarBottomSpacing = avatarBottomSpacing
copy.avatarContainerSpacing = avatarContainerSpacing
copy.avatarBottomPadding = avatarBottomPadding
copy.avatarMessagePadding = avatarMessagePadding
copy.direction = direction
return copy
// swiftlint:enable force_cast
}
+13 -1
View File
@@ -28,12 +28,16 @@ public protocol MessagesDisplayDataSource: class, MessagesDataSource {
func messageColorFor(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
func avatarForMessage(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarView
func avatarForMessage(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar
func headerForMessage(_ message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView?
func footerForMessage(_ 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 {
@@ -50,4 +54,12 @@ public extension MessagesDisplayDataSource {
return nil
}
func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
return nil
}
func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
return nil
}
}
+15 -17
View File
@@ -25,7 +25,7 @@ SOFTWARE.
import UIKit
open class MessagesViewController: UIViewController {
// MARK: - Properties
open var messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout())
@@ -65,15 +65,14 @@ open class MessagesViewController: UIViewController {
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// depends on inputAccessoryView frame thus must be called here
addKeyboardObservers()
messagesCollectionView.scrollToBottom(animated: false)
}
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeKeyboardObservers()
}
// MARK: - Initializers
deinit {
removeKeyboardObservers()
}
// MARK: - Methods
@@ -114,7 +113,7 @@ open class MessagesViewController: UIViewController {
toItem: view, attribute: .trailing, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: messagesCollectionView, attribute: .bottom, relatedBy: .equal,
toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: -48))
toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: -46))
}
@@ -133,8 +132,6 @@ extension MessagesViewController: UICollectionViewDelegateFlowLayout {
}
//swiftlint:enable line_length
// MARK: - UICollectionViewDataSource Conformance
extension MessagesViewController: UICollectionViewDataSource {
@@ -155,8 +152,6 @@ extension MessagesViewController: UICollectionViewDataSource {
}
//swiftlint:disable line_length
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MessageCell", for: indexPath) as? MessageCollectionViewCell ?? MessageCollectionViewCell()
@@ -172,8 +167,12 @@ extension MessagesViewController: UICollectionViewDataSource {
let message = displayDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
let messageColor = displayDataSource.messageColorFor(message, at: indexPath, in: messagesCollectionView)
let avatar = displayDataSource.avatarForMessage(message, at: indexPath, in: messagesCollectionView)
//TODO: replace this completely
cell.avatarImageView.image = avatar.getImage()
let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath)
let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
cell.cellTopLabel.attributedText = topLabelText
cell.cellBottomLabel.attributedText = bottomLabelText
cell.avatarView.set(avatar: avatar)
cell.messageContainerView.backgroundColor = messageColor
cell.configure(with: message)
@@ -219,8 +218,6 @@ extension MessagesViewController: UICollectionViewDataSource {
return messagesLayoutDelegate.footerSizeFor(message, at: indexPath, in: messagesCollectionView)
}
//swiftlint:enable line_length
}
// MARK: - Keyboard Handling
@@ -252,7 +249,8 @@ extension MessagesViewController {
let keyboardRect = keyboardSizeValue.cgRectValue
let messageInputBarHeight = inputAccessoryView?.bounds.size.height ?? 0
let keyboardHeight = keyboardRect.height - messageInputBarHeight
let keyboardHeight = keyboardRect.height - messageInputBarHeight
print(keyboardHeight)
messagesCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
}
@@ -0,0 +1,45 @@
/*
MIT License
Copyright (c) 2017 MessageKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import Foundation
extension NSAttributedString {
func height(considering width: CGFloat) -> CGFloat {
let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude)
let rect = self.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil)
return rect.height
}
func width(considering height: CGFloat) -> CGFloat {
let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
let rect = self.boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil)
return rect.width
}
}
+2
View File
@@ -32,4 +32,6 @@ extension UIColor {
static let inputBarGray = UIColor(colorLiteralRed: 247/255, green: 247/255, blue: 247/255, alpha: 1.0)
static let sendButtonBlue = UIColor(colorLiteralRed: 15/255, green: 135/255, blue: 255/255, alpha: 1.0)
}
+53 -47
View File
@@ -1,10 +1,26 @@
//
// AvatarViewTests.swift
// MessageKit
//
// Created by Dan Leonard on 8/5/17.
// Copyright © 2017 Hexed Bits. All rights reserved.
//
/*
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
@@ -20,61 +36,51 @@ class AvatarViewTests: XCTestCase {
}
func testNoParams() {
let avatar = AvatarView()
XCTAssertEqual(avatar.initals, "?")
XCTAssertEqual(avatar.initalsLabel.text, "?")
XCTAssertEqual(avatar.layer.cornerRadius, 15.0)
XCTAssertNil(avatar.imageView.image)
XCTAssertTrue(avatar.imageView.isHidden)
XCTAssertEqual(avatar.initalsLabel.textColor, UIColor.white)
let avatarView = AvatarView()
XCTAssertEqual(avatarView.avatar.initals, "?")
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
}
func testWithImage() {
let avatar = AvatarView(image: UIImage())
let avatarView = AvatarView()
let avatar = Avatar(image: UIImage())
avatarView.set(avatar: avatar)
XCTAssertEqual(avatar.initals, "?")
XCTAssertEqual(avatar.initalsLabel.text, "?")
XCTAssertEqual(avatar.layer.cornerRadius, 15.0)
XCTAssertFalse(avatar.imageView.isHidden)
XCTAssertEqual(avatar.initalsLabel.textColor, UIColor.white)
XCTAssertEqual(avatar.backgroundColor, UIColor.gray)
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
}
func testCustom() {
let avatar = AvatarView(size: 50, image: UIImage(), highlightedImage: nil, initals: "lol", cornerRounding: 6)
XCTAssertEqual(avatar.initals, "lol")
XCTAssertEqual(avatar.initalsLabel.text, "lol")
XCTAssertEqual(avatar.layer.cornerRadius, 6.0)
XCTAssertFalse(avatar.imageView.isHidden)
XCTAssertEqual(avatar.initalsLabel.textColor, UIColor.white)
XCTAssertEqual(avatar.backgroundColor, UIColor.gray)
func testInitalsOnly() {
let avatarView = AvatarView()
let avatar = Avatar(initals: "DL")
avatarView.set(avatar: avatar)
XCTAssertEqual(avatar.initals, "DL")
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
}
func testSetBackground() {
let avatar = AvatarView(image: UIImage())
XCTAssertEqual(avatar.backgroundColor, UIColor.gray)
avatar.setBackground(color: UIColor.red)
XCTAssertEqual(avatar.backgroundColor, UIColor.red)
let avatarView = AvatarView()
XCTAssertEqual(avatarView.backgroundColor, UIColor.gray)
avatarView.setBackground(color: UIColor.red)
XCTAssertEqual(avatarView.backgroundColor, UIColor.red)
}
func testGetImage() {
let image = UIImage()
let avatar = AvatarView(image: image)
XCTAssertEqual(avatar.getImage(), image)
let avatar = Avatar(image: image)
let avatarView = AvatarView()
avatarView.set(avatar: avatar)
XCTAssertEqual(avatarView.getImage(), image)
}
func testRoundedCorners() {
let avatar = AvatarView(image: UIImage())
XCTAssertEqual(avatar.layer.cornerRadius, 15.0)
avatar.setCorner(radius: 2)
XCTAssertEqual(avatar.layer.cornerRadius, 2.0)
}
func testInitalsFont() {
let avatar = AvatarView(image: UIImage())
XCTAssertEqual(avatar.initalsLabel.textColor, UIColor.white)
XCTAssertEqual(avatar.initalsLabel.font.pointSize, 16)
avatar.setInitalsFont(size: 20, color: UIColor.blue)
XCTAssertEqual(avatar.initalsLabel.textColor, UIColor.blue)
XCTAssertEqual(avatar.initalsLabel.font.pointSize, 20)
let avatarView = AvatarView()
let avatar = Avatar(image: UIImage())
avatarView.set(avatar: avatar)
XCTAssertEqual(avatarView.layer.cornerRadius, 15.0)
avatarView.setCorner(radius: 2)
XCTAssertEqual(avatarView.layer.cornerRadius, 2.0)
}
}
+23 -17
View File
@@ -1,20 +1,26 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://messagekit.github.io
//
//
// GitHub
// https://github.com/MessageKit/MessageKit
//
//
// License
// Copyright (c) 2016-present Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
/*
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