From 5d79572fd912828dc6f59ee7e28acda6f230fe02 Mon Sep 17 00:00:00 2001 From: MacmeDan Date: Mon, 7 Aug 2017 17:46:22 -0600 Subject: [PATCH 1/2] Added the `Avatar` struct which has an image and initials. Added an `Avatar` property to the `AvatarView` Replaced the `imageView` in the `MessagesCollectionViewCell` with the `AvatarView` Moved the `Avatar.playground` to the example project so users can play around and learn it capabilities. Updated the example project and abstracted the `sampleData` to its own file. Updated the organization for the project to `MessageKit` instead of `Hex bits` --- Example/ChatExample.xcodeproj/project.pbxproj | 8 +- .../Dan-Leonard.imageset/Contents.json | 21 ++++ .../Dan-Leonard.imageset}/NiceSelfi.jpg | Bin Example/Sources/Base.lproj/Main.storyboard | 95 +----------------- .../Sources/ConversationViewController.swift | 70 ++----------- Example/Sources/InboxViewController.swift | 21 +++- Example/Sources/MockMessage.swift | 4 +- .../Avatar.playground/Contents.swift | 29 ++++++ .../Avatar.playground/Resources/NiceSelfi.jpg | Bin 0 -> 41110 bytes .../Avatar.playground}/contents.xcplayground | 2 +- .../Avatar.playground/timeline.xctimeline | 6 ++ Example/Sources/SampleData.swift | 46 +++++++++ MessageKit.xcodeproj/project.pbxproj | 8 +- Sources/Avatar.swift | 20 ++++ Sources/AvatarView.playground/Contents.swift | 37 ------- Sources/AvatarView.swift | 78 +++++++------- Sources/MessageCollectionViewCell.swift | 12 +-- Sources/MessagesDisplayDataSource.swift | 2 +- Sources/MessagesViewController.swift | 5 +- 19 files changed, 207 insertions(+), 257 deletions(-) create mode 100644 Example/Sources/Assets.xcassets/Dan-Leonard.imageset/Contents.json rename {Sources/AvatarView.playground/Resources => Example/Sources/Assets.xcassets/Dan-Leonard.imageset}/NiceSelfi.jpg (100%) create mode 100644 Example/Sources/Playgrounds/Avatar.playground/Contents.swift create mode 100644 Example/Sources/Playgrounds/Avatar.playground/Resources/NiceSelfi.jpg rename {Sources/AvatarView.playground => Example/Sources/Playgrounds/Avatar.playground}/contents.xcplayground (61%) create mode 100644 Example/Sources/Playgrounds/Avatar.playground/timeline.xctimeline create mode 100644 Example/Sources/SampleData.swift create mode 100644 Sources/Avatar.swift delete mode 100644 Sources/AvatarView.playground/Contents.swift diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 550cf04b..432525d2 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -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 = ""; }; + 37F8BCD41F38F3A8003C12C2 /* Avatar.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = Avatar.playground; path = Playgrounds/Avatar.playground; sourceTree = ""; }; 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 */, diff --git a/Example/Sources/Assets.xcassets/Dan-Leonard.imageset/Contents.json b/Example/Sources/Assets.xcassets/Dan-Leonard.imageset/Contents.json new file mode 100644 index 00000000..634187f5 --- /dev/null +++ b/Example/Sources/Assets.xcassets/Dan-Leonard.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/Sources/AvatarView.playground/Resources/NiceSelfi.jpg b/Example/Sources/Assets.xcassets/Dan-Leonard.imageset/NiceSelfi.jpg similarity index 100% rename from Sources/AvatarView.playground/Resources/NiceSelfi.jpg rename to Example/Sources/Assets.xcassets/Dan-Leonard.imageset/NiceSelfi.jpg diff --git a/Example/Sources/Base.lproj/Main.storyboard b/Example/Sources/Base.lproj/Main.storyboard index 1c6fff35..f7cdb8d6 100644 --- a/Example/Sources/Base.lproj/Main.storyboard +++ b/Example/Sources/Base.lproj/Main.storyboard @@ -1,17 +1,18 @@ - + - + + @@ -25,9 +26,6 @@ - - - @@ -35,30 +33,11 @@ - + - - - - - - - - - - - - - - - - - - - @@ -76,71 +55,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index d34af97f..1adf1277 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -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? { @@ -159,13 +109,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() - } } diff --git a/Example/Sources/InboxViewController.swift b/Example/Sources/InboxViewController.swift index 31ede66e..4f237127 100644 --- a/Example/Sources/InboxViewController.swift +++ b/Example/Sources/InboxViewController.swift @@ -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 + } + } } diff --git a/Example/Sources/MockMessage.swift b/Example/Sources/MockMessage.swift index 95e7bbfd..355eb82d 100644 --- a/Example/Sources/MockMessage.swift +++ b/Example/Sources/MockMessage.swift @@ -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() } diff --git a/Example/Sources/Playgrounds/Avatar.playground/Contents.swift b/Example/Sources/Playgrounds/Avatar.playground/Contents.swift new file mode 100644 index 00000000..6292a77e --- /dev/null +++ b/Example/Sources/Playgrounds/Avatar.playground/Contents.swift @@ -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 diff --git a/Example/Sources/Playgrounds/Avatar.playground/Resources/NiceSelfi.jpg b/Example/Sources/Playgrounds/Avatar.playground/Resources/NiceSelfi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bec3cfeca48df384efb960dba611610227bedcb6 GIT binary patch literal 41110 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 literal 0 HcmV?d00001 diff --git a/Sources/AvatarView.playground/contents.xcplayground b/Example/Sources/Playgrounds/Avatar.playground/contents.xcplayground similarity index 61% rename from Sources/AvatarView.playground/contents.xcplayground rename to Example/Sources/Playgrounds/Avatar.playground/contents.xcplayground index 5da2641c..89da2d47 100644 --- a/Sources/AvatarView.playground/contents.xcplayground +++ b/Example/Sources/Playgrounds/Avatar.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Example/Sources/Playgrounds/Avatar.playground/timeline.xctimeline b/Example/Sources/Playgrounds/Avatar.playground/timeline.xctimeline new file mode 100644 index 00000000..bf468afe --- /dev/null +++ b/Example/Sources/Playgrounds/Avatar.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift new file mode 100644 index 00000000..06fce8ff --- /dev/null +++ b/Example/Sources/SampleData.swift @@ -0,0 +1,46 @@ +// +// SampleData.swift +// ChatExample +// +// Created by Dan Leonard on 8/7/17. +// Copyright © 2017 MessageKit. All rights reserved. +// + +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 don’t cost any money. It’s really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString) + let msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Cook, messageId: UUID().uuidString) + let msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.", sender: Jobs, messageId: UUID().uuidString) + + return [msg1, msg2, msg3, msg4, msg5, msg6] + } + + 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() + } + } +} diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 3a25e9fc..1176f005 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 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 */; }; @@ -45,8 +46,8 @@ /* Begin PBXFileReference section */ 372F6AEA1F36C15600B57FBD /* AvatarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; - 372F6AED1F36C1C100B57FBD /* AvatarView.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = AvatarView.playground; sourceTree = ""; }; 372F6AEE1F36C61000B57FBD /* AvatarViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarViewTests.swift; sourceTree = ""; }; + 37C936971F38F6AC00853DF2 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = ""; }; 882D75831DE507320033F95F /* MessagesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDataSource.swift; sourceTree = ""; }; 888CEBFB1D3FD525005178DE /* MessagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = ""; }; 88916B221CF0DF2F00469F91 /* MessageKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -149,6 +150,7 @@ children = ( B0655A291F23D77200542A83 /* Sender.swift */, B0655A2B1F23D81600542A83 /* MessageData.swift */, + 37C936971F38F6AC00853DF2 /* Avatar.swift */, B0655A271F23D71400542A83 /* MessageDirection.swift */, ); name = Models; @@ -160,7 +162,6 @@ B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */, B0655A4C1F244C0600542A83 /* MessageCollectionViewCell.swift */, 372F6AEA1F36C15600B57FBD /* AvatarView.swift */, - 372F6AED1F36C1C100B57FBD /* AvatarView.playground */, B0655A371F23EE8B00542A83 /* MessageInputBar.swift */, B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */, B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */, @@ -266,7 +267,7 @@ attributes = { LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0810; - ORGANIZATIONNAME = "Hexed Bits"; + ORGANIZATIONNAME = MessageKit; TargetAttributes = { 88916B211CF0DF2F00469F91 = { CreatedOnToolsVersion = 7.3.1; @@ -351,6 +352,7 @@ 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 */, ); diff --git a/Sources/Avatar.swift b/Sources/Avatar.swift new file mode 100644 index 00000000..e9bb305a --- /dev/null +++ b/Sources/Avatar.swift @@ -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 + } + +} diff --git a/Sources/AvatarView.playground/Contents.swift b/Sources/AvatarView.playground/Contents.swift deleted file mode 100644 index e5e0b2d8..00000000 --- a/Sources/AvatarView.playground/Contents.swift +++ /dev/null @@ -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 diff --git a/Sources/AvatarView.swift b/Sources/AvatarView.swift index 79c41c96..7f91caab 100644 --- a/Sources/AvatarView.swift +++ b/Sources/AvatarView.swift @@ -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,35 @@ 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)) + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Color Declarations + let white = UIColor.white + + //// 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: white, 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 +71,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 +100,10 @@ open class AvatarView: UIView { } layer.cornerRadius = radius } + + // MARK: - Open getters + + open func getImage() -> UIImage? { + return imageView.image + } } diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index baf2ac95..3f816f85 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -29,25 +29,15 @@ 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 = { - - let avatarImageView = UIImageView() - avatarImageView.contentMode = .scaleAspectFill - avatarImageView.backgroundColor = .lightGray - avatarImageView.layer.masksToBounds = true - avatarImageView.clipsToBounds = true - return avatarImageView - }() + open var avatarImageView: AvatarView = AvatarView() open let messageLabel: UILabel = { - let messageLabel = UILabel() messageLabel.numberOfLines = 0 messageLabel.backgroundColor = .clear diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDataSource.swift index 067df3b6..35c92b82 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDataSource.swift @@ -28,7 +28,7 @@ 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? diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 6e4ea2df..1c31691c 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -25,7 +25,7 @@ SOFTWARE. import UIKit open class MessagesViewController: UIViewController { - + // MARK: - Properties open var messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout()) @@ -172,8 +172,7 @@ 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() + cell.avatarImageView.set(avatar: avatar) cell.messageContainerView.backgroundColor = messageColor cell.configure(with: message) From 7f83b2c0bc363290117dc2f4c36037f37f6dccdb Mon Sep 17 00:00:00 2001 From: MacmeDan Date: Mon, 7 Aug 2017 18:00:37 -0600 Subject: [PATCH 2/2] Fixed tests --- Sources/AvatarView.swift | 6 +--- Tests/AvatarViewTests.swift | 70 ++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/Sources/AvatarView.swift b/Sources/AvatarView.swift index 7f91caab..537cc32a 100644 --- a/Sources/AvatarView.swift +++ b/Sources/AvatarView.swift @@ -43,17 +43,13 @@ open class AvatarView: UIView { func getImageFrom(initals: String, withColor color: UIColor = UIColor.white, fontSize: CGFloat = 14) -> UIImage { _ = UIGraphicsBeginImageContext(CGSize(width: 30, height: 30)) - //// General Declarations let context = UIGraphicsGetCurrentContext()! - //// Color Declarations - let white = UIColor.white - //// 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: white, NSParagraphStyleAttributeName: textStyle] + 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() diff --git a/Tests/AvatarViewTests.swift b/Tests/AvatarViewTests.swift index 1387f5e4..0c089d3a 100644 --- a/Tests/AvatarViewTests.swift +++ b/Tests/AvatarViewTests.swift @@ -20,61 +20,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) } }