From 447dc6b10db6b4f77fe9ff7d94cb05c77018ec65 Mon Sep 17 00:00:00 2001 From: Omer YALHI Date: Fri, 1 Sep 2017 17:11:14 +0300 Subject: [PATCH 01/43] MessageContainerView.swift added --- MessageKit.xcodeproj/project.pbxproj | 4 + Sources/MessageContainerView.swift | 298 +++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 Sources/MessageContainerView.swift diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 75cecdf8..1bb6125c 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438F1F289142004D0129 /* UIColor+Extensions.swift */; }; B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */; }; B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */; }; + E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -91,6 +92,7 @@ B096438F1F289142004D0129 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = ""; }; B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; + E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContainerView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -199,6 +201,7 @@ B074EE921F35587100ABB8C8 /* MessageHeaderView.swift */, B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */, B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */, + E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */, ); name = Views; sourceTree = ""; @@ -373,6 +376,7 @@ B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */, 882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */, 888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */, + E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */, B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */, B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */, B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */, diff --git a/Sources/MessageContainerView.swift b/Sources/MessageContainerView.swift new file mode 100644 index 00000000..bc3910ab --- /dev/null +++ b/Sources/MessageContainerView.swift @@ -0,0 +1,298 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import UIKit + +open class MessageContainerView: UIView { + + var style: MessageStyle = .none { + didSet { + // ***** IMPORTANT ******** + // do not check against oldValue and skip updating as the view gets created in cell and it is dequeued, + // it gets created for a limited number of times but gets used many times, + // therefore we need to draw it each time the style is set + // DO NOT DO THIS --> guard style != oldValue else { return } + setNeedsDisplay() + } + } + + fileprivate var tailHeight: CGFloat! + fileprivate var tailWidth: CGFloat! + + open override func draw(_ rect: CGRect) { + switch style { + case .bubble(let border, let tailStyle): + tailHeight = border.cornerRadius + tailWidth = border.cornerRadius / 2 + drawBubble(in: rect, with: border, using: tailStyle) + case .none: + self.layer.cornerRadius = 0 + self.layer.masksToBounds = false + self.layer.borderWidth = 0 + self.layer.borderColor = nil + } + } + + private func drawBubble(in rect: CGRect, with border: MessageBorder, using tailStyle: MessageBubbleTailStyle) { + let tailPath: UIBezierPath + let outlinePath: UIBezierPath + let bubbleTailBorder: MessageBorder? + + switch tailStyle { + case .none: + tailPath = UIBezierPath() + outlinePath = UIBezierPath(roundedRect: rect, cornerRadius: border.cornerRadius) + bubbleTailBorder = nil + case .triangle(let corner, let tailBorder): + (tailPath, outlinePath) = createTriangleTailedBubble(for: rect, with: border, for: corner) + bubbleTailBorder = tailBorder + case .tailCurved(let corner, let tailBorder): + (tailPath, outlinePath) = createTailCurvedTailedBubble(for: rect, with: border, for: corner) + bubbleTailBorder = tailBorder + } + + border.fillColor.setFill() + outlinePath.fill() + + if let bubbleTailBorder = bubbleTailBorder { + bubbleTailBorder.fillColor.setFill() + tailPath.fill() + if let outlineColor = bubbleTailBorder.color { + outlineColor.setStroke() + tailPath.lineWidth = bubbleTailBorder.width + tailPath.stroke() + } + } else { + tailPath.fill() + } + + if let borderColor = border.color { + borderColor.setStroke() + outlinePath.lineWidth = border.width + outlinePath.stroke() + } + } + + private func createTriangleTailedBubble(for rect: CGRect, with border: MessageBorder, for corner: UIRectCorner) -> (tailPath: UIBezierPath, outlinePath: UIBezierPath) { + let tailPath = UIBezierPath() + switch corner { + case UIRectCorner.bottomLeft: + tailPath.move(to: CGPoint(x: rect.minX, y: rect.maxY)) + tailPath.addLine(to: CGPoint(x: rect.minX, y: rect.maxY-border.cornerRadius)) + tailPath.addLine(to: CGPoint(x: rect.minX+border.cornerRadius, y: rect.maxY)) + case UIRectCorner.bottomRight: + tailPath.move(to: CGPoint(x: rect.maxX, y: rect.maxY)) + tailPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY-border.cornerRadius)) + tailPath.addLine(to: CGPoint(x: rect.maxX-border.cornerRadius, y: rect.maxY)) + case UIRectCorner.topLeft: + tailPath.move(to: CGPoint(x: rect.minX, y: rect.minY)) + tailPath.addLine(to: CGPoint(x: rect.minX, y: rect.minY+border.cornerRadius)) + tailPath.addLine(to: CGPoint(x: rect.minX+border.cornerRadius, y: rect.minY)) + case UIRectCorner.topRight: + tailPath.move(to: CGPoint(x: rect.maxX, y: rect.minY)) + tailPath.addLine(to: CGPoint(x: rect.maxX, y: rect.minY+border.cornerRadius)) + tailPath.addLine(to: CGPoint(x: rect.maxX-border.cornerRadius, y: rect.minY)) + default: + fatalError("FATAL ERROR unhandled UIRectCorner") + } + + tailPath.close() + let outlinePath = UIBezierPath(roundedRect: rect, + byRoundingCorners: [.allCorners], + cornerRadii: CGSize(width: border.cornerRadius, height: 0)) + return (tailPath, outlinePath) + } + +} + +// MARK: - Style 1 related +extension MessageContainerView { + fileprivate func createTailCurvedTailedBubble(for rect: CGRect, with border: MessageBorder, for corner: UIRectCorner) -> (tailPath: UIBezierPath, outlinePath: UIBezierPath) { + let tailPath: UIBezierPath + let insetRect: CGRect + + switch corner { + case UIRectCorner.bottomLeft: + let widthInset = UIEdgeInsets(top: 0, left: tailWidth, bottom: 0, right: 0) + insetRect = UIEdgeInsetsInsetRect(rect, widthInset) + tailPath = tailCurvedTailPathForBottomLeft(using: insetRect) + + case UIRectCorner.bottomRight: + let widthInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: tailWidth) + insetRect = UIEdgeInsetsInsetRect(rect, widthInset) + tailPath = tailCurvedTailPathForBottomLeft(using: insetRect) + tailPath.mirrorOnXAxis(in: insetRect) + + case UIRectCorner.topLeft: + let widthInset = UIEdgeInsets(top: 0, left: tailWidth, bottom: 0, right: 0) + insetRect = UIEdgeInsetsInsetRect(rect, widthInset) + tailPath = tailCurvedTailPathForTopLeft(using: insetRect) + + case UIRectCorner.topRight: + let widthInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: tailWidth) + insetRect = UIEdgeInsetsInsetRect(rect, widthInset) + tailPath = tailCurvedTailPathForTopLeft(using: insetRect) + tailPath.mirrorOnXAxis(in: insetRect) + + default: fatalError("FATAL ERROR unhandled UIRectCorner") + } + + let outlinePath = UIBezierPath(roundedRect: insetRect, + byRoundingCorners: [.allCorners], + cornerRadii: CGSize(width: border.cornerRadius, height: 0)) + return (tailPath, outlinePath) + + } + + private func tailCurvedTailPathForBottomLeft(using insetRect: CGRect) -> UIBezierPath { + let tailPath = UIBezierPath() + let tailStartPoint = CGPoint(x: insetRect.minX, y: insetRect.maxY - tailHeight) + let tailBottomPoint = CGPoint(x: insetRect.minX - tailWidth, y: insetRect.maxY) + let controlPoint1 = CGPoint(x: tailHeight/2, y: insetRect.maxY - tailWidth) + tailPath.move(to: tailStartPoint) + tailPath.addQuadCurve(to: tailBottomPoint, controlPoint: controlPoint1) + let tailMiddlePoint = CGPoint(x: tailWidth * 2, y: insetRect.maxY - ((tailWidth * 2)/3)) + let controlPoint2 = CGPoint(x: tailWidth, y: insetRect.maxY - 1) + tailPath.addQuadCurve(to: tailMiddlePoint, controlPoint: controlPoint2) + tailPath.close() + return tailPath + } + + private func tailCurvedTailPathForTopLeft(using insetRect: CGRect) -> UIBezierPath { + let tailPath = UIBezierPath() + let tailStartPoint = CGPoint(x: insetRect.minX, y: insetRect.minY + tailHeight) + let tailBottomPoint = CGPoint(x: insetRect.minX - tailWidth, y: insetRect.minY) + let controlPoint1 = CGPoint(x: tailHeight/2, y: insetRect.minY + tailWidth) + tailPath.move(to: tailStartPoint) + tailPath.addQuadCurve(to: tailBottomPoint, controlPoint: controlPoint1) + let tailMiddlePoint = CGPoint(x: tailWidth * 2, y: insetRect.minY + ((tailWidth * 2)/3)) + let controlPoint2 = CGPoint(x: tailWidth, y: insetRect.minY + 1) + tailPath.addQuadCurve(to: tailMiddlePoint, controlPoint: controlPoint2) + tailPath.close() + return tailPath + } + +} + +public enum MessageBubbleTailStyle { + case none + case triangle(corner: UIRectCorner, border: MessageBorder) + case tailCurved(corner: UIRectCorner, border: MessageBorder) // we should find a proper name for this + + public static let styleCount = 2 +} + +extension MessageBubbleTailStyle: CustomDebugStringConvertible { + public var name: String { + switch self { + case .none: return "None" + case .triangle: return "Triangle" + case .tailCurved: return "TailCurved" + } + } + + public var debugDescription: String { + switch self { + case .triangle(let corner, let border): + return "name: \(name), corner: \(corner), border: \(border)" + case .tailCurved(let corner): + return "name: \(name), corner: \(corner)" + default: + return "name: \(name)" + } + } +} + +public struct MessageBorder { + let color: UIColor? + let width: CGFloat + let cornerRadius: CGFloat + let fillColor: UIColor + + public init(cornerRadius: CGFloat = 15, color: UIColor? = nil, width: CGFloat = 0, fillColor: UIColor) { + self.color = color + self.width = width + self.cornerRadius = cornerRadius + self.fillColor = fillColor + } +} + +extension MessageBorder: CustomDebugStringConvertible { + public var debugDescription: String { + return "color: \(color.debugDescription), width: \(width), cornerRadius: \(cornerRadius)" + } +} + +public enum MessageStyle { + case none + case bubble(border: MessageBorder, withTail: MessageBubbleTailStyle) +} + +extension MessageStyle { + public var name: String { + switch self { + case .bubble: return "Bubble" + case .none: return "None" + } + } +} + +extension MessageStyle: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .bubble(let border, let tailStyle): + return "MessageStyle: Bubble, border: \(border.debugDescription), tailStyle: \(tailStyle.debugDescription)" + case .none: + return "MessageStyle: none" + } + } +} + +extension MessageStyle: Equatable { + static public func == (lhs: MessageStyle, rhs: MessageStyle) -> Bool { + return lhs.name == rhs.name + } +} + +public extension UIImage { + public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} + +public extension UIBezierPath { + public func mirrorOnXAxis(in rect: CGRect) { + apply(CGAffineTransform(translationX: -rect.origin.x, y: 0)) + apply(CGAffineTransform(scaleX: -1, y: 1)) + apply(CGAffineTransform(translationX: rect.origin.x+rect.width, y: 0)) + } +} From bc217760788f83921ca680c99ca1af114e07ab8e Mon Sep 17 00:00:00 2001 From: Omer YALHI Date: Sat, 2 Sep 2017 09:50:59 +0300 Subject: [PATCH 02/43] MessageContainerView updates for the MessageKit project --- Sources/MessageCollectionViewCell.swift | 7 +------ Sources/MessagesDisplayDataSource.swift | 6 ++++++ Sources/MessagesViewController.swift | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index 5bf2bff5..4e15feaa 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -28,12 +28,7 @@ open class MessageCollectionViewCell: UICollectionViewCell { // MARK: - Properties - open var messageContainerView: UIView = { - let messageContainerView = UIView() - messageContainerView.layer.cornerRadius = 12.0 - messageContainerView.layer.masksToBounds = true - return messageContainerView - }() + open var messageContainerView: MessageContainerView = MessageContainerView() open var avatarView: AvatarView = AvatarView() diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDataSource.swift index ab750b09..5706b0c1 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDataSource.swift @@ -27,6 +27,8 @@ import Foundation public protocol MessagesDisplayDataSource: class, MessagesDataSource { func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor + + func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor @@ -50,6 +52,10 @@ public extension MessagesDisplayDataSource { return isFromCurrentSender(message: message) ? .white : .black } + func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { + return .none + } + func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index f1fba2fd..3a91ffd6 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -171,6 +171,7 @@ extension MessagesViewController: UICollectionViewDataSource { if let displayDataSource = messagesDataSource as? MessagesDisplayDataSource { let messageColor = displayDataSource.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) + let messageStyle = displayDataSource.messageStyle(for: message, at: indexPath, in: messagesCollectionView) let textColor = displayDataSource.textColor(for: message, at: indexPath, in: messagesCollectionView) let avatar = displayDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView) let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath) @@ -179,6 +180,7 @@ extension MessagesViewController: UICollectionViewDataSource { cell.avatarView.set(avatar: avatar) cell.messageLabel.textColor = textColor cell.messageContainerView.backgroundColor = messageColor + cell.messageContainerView.style = messageStyle cell.cellTopLabel.attributedText = topLabelText cell.cellBottomLabel.attributedText = bottomLabelText From 37b6adbc75c581e513ffab10c0e6462f3c7d959a Mon Sep 17 00:00:00 2001 From: Omer YALHI Date: Sat, 2 Sep 2017 09:59:50 +0300 Subject: [PATCH 03/43] Final updates --- .../Sources/ConversationViewController.swift | 30 +++++++++++++++++++ Sources/MessagesViewController.swift | 6 ++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index de9d5bb3..fabec2d4 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -72,6 +72,12 @@ extension ConversationViewController: MessagesDisplayDataSource { return .messageTop } + func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { + let lightGray = UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1) + let bubbleBorder = MessageBorder(cornerRadius: 15, color: UIColor.clear, width: 0, fillColor: lightGray) + return .bubble(border: bubbleBorder, withTail: randomTailStyle()) + } + func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { return messagesCollectionView.dequeueMessageHeaderView(for: indexPath) } @@ -94,6 +100,30 @@ extension ConversationViewController: MessagesDisplayDataSource { } +// MARK: - Helper Methods for random bubble and tail generation + +extension ConversationViewController { + fileprivate func randomTailStyle() -> MessageBubbleTailStyle { + let lightGray = UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1) + let tailBorderNoColor = MessageBorder(cornerRadius: 0, color: nil, width: 0, fillColor: lightGray) + let tailBorderColored = MessageBorder(cornerRadius: 0, color: UIColor.lightGray, width: 1, fillColor: UIColor.orange) + let randomStyle = Int(arc4random_uniform(UInt32(MessageBubbleTailStyle.styleCount))) + let isColoredBorder = Int(arc4random_uniform(2)) == 1 + + switch randomStyle { + case 0: return .triangle(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) + case 1: return .tailCurved(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) + default: assert(false); break + } + } + + fileprivate func randomCorner() -> UIRectCorner { + var corners: [UIRectCorner] = [.bottomLeft, .bottomRight, .topLeft, .topRight] + let random = Int(arc4random_uniform(4)) + return corners[random] + } +} + // MARK: - MessagesLayoutDelegate extension ConversationViewController: MessagesLayoutDelegate { diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 3a91ffd6..54581f4f 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -169,8 +169,10 @@ extension MessagesViewController: UICollectionViewDataSource { let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView) if let displayDataSource = messagesDataSource as? MessagesDisplayDataSource { - - let messageColor = displayDataSource.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) + // MessageContainerView needs to know the color. Currently it doesn't, so we are ignoring the message color here + // And using an arbitrary color in the container view + // As of now, the background color can not be changed! + let messageColor = UIColor.clear let messageStyle = displayDataSource.messageStyle(for: message, at: indexPath, in: messagesCollectionView) let textColor = displayDataSource.textColor(for: message, at: indexPath, in: messagesCollectionView) let avatar = displayDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView) From 36561457da002dd4dbdb9d610c01c8e6b8ccbad7 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 2 Sep 2017 21:49:59 -0500 Subject: [PATCH 04/43] Add assets bundle --- Assets/Assets.xcassets/Contents.json | 6 + .../bubble_tailed.imageset/Contents.json | 21 ++++ .../bubble_tailed.imageset/bubble_tailed.png | Bin 0 -> 380 bytes Assets/Info.plist | 26 ++++ MessageKit.xcodeproj/project.pbxproj | 116 ++++++++++++++++++ 5 files changed, 169 insertions(+) create mode 100644 Assets/Assets.xcassets/Contents.json create mode 100644 Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json create mode 100644 Assets/Assets.xcassets/bubble_tailed.imageset/bubble_tailed.png create mode 100644 Assets/Info.plist diff --git a/Assets/Assets.xcassets/Contents.json b/Assets/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Assets/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json b/Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json new file mode 100644 index 00000000..ce1e2474 --- /dev/null +++ b/Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "bubble_tailed.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Assets/Assets.xcassets/bubble_tailed.imageset/bubble_tailed.png b/Assets/Assets.xcassets/bubble_tailed.imageset/bubble_tailed.png new file mode 100644 index 0000000000000000000000000000000000000000..f4e7f93d139361d5a31b0440f385439ab6aebbf4 GIT binary patch literal 380 zcmV-?0fYXDP)u`7PaCe869+wI!afjd#_nRv6T+hx_t5fa0d$33# zdMX)Vo>lfa;f!}io^irHtIRV(C3*sIlBZ;_jB>&y(s_bI=qTa?$_3w(kKKf^jB&*1>qKgAe(xZ4kP(4*CfPuepm6Euz8CJi>@uoQNiaFd)|fAX96Ckf&_~b@GD{|q@Pu4 a@ca!u+4^?6ZMG`_0000 + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright © 2017 MessageKit. All rights reserved. + NSPrincipalClass + + + diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 1940c7c8..0366572f 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; + B01E2DD41F5BA48300E4FA1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */; }; + B01E2DD51F5BA49100E4FA1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */; }; B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */; }; B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05530B41F493CFA008BB420 /* DetectorType.swift */; }; B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A271F23D71400542A83 /* MessageDirection.swift */; }; @@ -55,6 +57,13 @@ remoteGlobalIDString = 88916B211CF0DF2F00469F91; remoteInfo = MessageKit; }; + B01E2DD11F5BA3E900E4FA1C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 88916B191CF0DF2F00469F91 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B01E2DCA1F5BA34700E4FA1C; + remoteInfo = Assets; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -80,6 +89,9 @@ B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; + B01E2DCB1F5BA34700E4FA1C /* Assets.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Assets.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + B01E2DCD1F5BA34700E4FA1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellDelegate.swift; sourceTree = ""; }; B05530B41F493CFA008BB420 /* DetectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = ""; }; B0655A271F23D71400542A83 /* MessageDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDirection.swift; sourceTree = ""; }; @@ -117,6 +129,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B01E2DC81F5BA34700E4FA1C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -132,6 +151,7 @@ 88916B181CF0DF2F00469F91 = { isa = PBXGroup; children = ( + B01E2DCC1F5BA34700E4FA1C /* Assets */, 88916B3C1CF0DF5100469F91 /* Sources */, 88916B411CF0DF5900469F91 /* Tests */, 88916B231CF0DF2F00469F91 /* Products */, @@ -143,6 +163,7 @@ children = ( 88916B221CF0DF2F00469F91 /* MessageKit.framework */, 88916B2C1CF0DF2F00469F91 /* MessageKitTests.xctest */, + B01E2DCB1F5BA34700E4FA1C /* Assets.bundle */, ); name = Products; sourceTree = ""; @@ -173,6 +194,15 @@ path = Tests; sourceTree = SOURCE_ROOT; }; + B01E2DCC1F5BA34700E4FA1C /* Assets */ = { + isa = PBXGroup; + children = ( + B01E2DCD1F5BA34700E4FA1C /* Info.plist */, + B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */, + ); + path = Assets; + sourceTree = ""; + }; B09643981F295D43004D0129 /* Extensions */ = { isa = PBXGroup; children = ( @@ -282,6 +312,7 @@ buildRules = ( ); dependencies = ( + B01E2DD21F5BA3E900E4FA1C /* PBXTargetDependency */, ); name = MessageKit; productName = MessageKit; @@ -306,6 +337,23 @@ productReference = 88916B2C1CF0DF2F00469F91 /* MessageKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + B01E2DCA1F5BA34700E4FA1C /* Assets */ = { + isa = PBXNativeTarget; + buildConfigurationList = B01E2DD01F5BA34700E4FA1C /* Build configuration list for PBXNativeTarget "Assets" */; + buildPhases = ( + B01E2DC71F5BA34700E4FA1C /* Sources */, + B01E2DC81F5BA34700E4FA1C /* Frameworks */, + B01E2DC91F5BA34700E4FA1C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Assets; + productName = Assets; + productReference = B01E2DCB1F5BA34700E4FA1C /* Assets.bundle */; + productType = "com.apple.product-type.bundle"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -323,6 +371,10 @@ 88916B2B1CF0DF2F00469F91 = { CreatedOnToolsVersion = 7.3.1; }; + B01E2DCA1F5BA34700E4FA1C = { + CreatedOnToolsVersion = 8.3.2; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 88916B1C1CF0DF2F00469F91 /* Build configuration list for PBXProject "MessageKit" */; @@ -339,6 +391,7 @@ targets = ( 88916B211CF0DF2F00469F91 /* MessageKit */, 88916B2B1CF0DF2F00469F91 /* MessageKitTests */, + B01E2DCA1F5BA34700E4FA1C /* Assets */, ); }; /* End PBXProject section */ @@ -348,6 +401,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B01E2DD51F5BA49100E4FA1C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -358,6 +412,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B01E2DC91F5BA34700E4FA1C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B01E2DD41F5BA48300E4FA1C /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -427,6 +489,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B01E2DC71F5BA34700E4FA1C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -435,6 +504,11 @@ target = 88916B211CF0DF2F00469F91 /* MessageKit */; targetProxy = 88916B2E1CF0DF2F00469F91 /* PBXContainerItemProxy */; }; + B01E2DD21F5BA3E900E4FA1C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B01E2DCA1F5BA34700E4FA1C /* Assets */; + targetProxy = B01E2DD11F5BA3E900E4FA1C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -598,6 +672,40 @@ }; name = Release; }; + B01E2DCE1F5BA34700E4FA1C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = NO; + INFOPLIST_FILE = Assets/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + PRODUCT_BUNDLE_IDENTIFIER = MessageKit.Assets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + B01E2DCF1F5BA34700E4FA1C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = NO; + INFOPLIST_FILE = Assets/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + PRODUCT_BUNDLE_IDENTIFIER = MessageKit.Assets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -628,6 +736,14 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + B01E2DD01F5BA34700E4FA1C /* Build configuration list for PBXNativeTarget "Assets" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B01E2DCE1F5BA34700E4FA1C /* Debug */, + B01E2DCF1F5BA34700E4FA1C /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = 88916B191CF0DF2F00469F91 /* Project object */; From b3aa820be4911d0ecb74581b8f2d4bacceb55866 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 3 Sep 2017 04:03:22 -0500 Subject: [PATCH 05/43] Add images for MessageBubbles --- Assets/Assets.xcassets/Contents.json | 6 - .../bubble_tailed.imageset/Contents.json | 21 -- Assets/Info.plist | 26 -- .../Images}/bubble_tailed.png | Bin .../Images/bubble_tailed@3x.png | Bin 0 -> 1177 bytes Assets/MessageKitAssets.bundle/Root.plist | 61 ++++ .../en.lproj/Root.strings | Bin 0 -> 546 bytes .../Sources/ConversationViewController.swift | 37 ++- MessageKit.xcodeproj/project.pbxproj | 135 ++------- Sources/Bundle+Extensions.swift | 36 +++ Sources/MessageContainerView.swift | 282 ++---------------- Sources/MessageStyle.swift | 91 ++++++ Sources/MessagesDisplayDataSource.swift | 2 +- Sources/MessagesViewController.swift | 8 +- ...toLayout.swift => UIView+Extensions.swift} | 0 15 files changed, 256 insertions(+), 449 deletions(-) delete mode 100644 Assets/Assets.xcassets/Contents.json delete mode 100644 Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json delete mode 100644 Assets/Info.plist rename Assets/{Assets.xcassets/bubble_tailed.imageset => MessageKitAssets.bundle/Images}/bubble_tailed.png (100%) create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_tailed@3x.png create mode 100644 Assets/MessageKitAssets.bundle/Root.plist create mode 100644 Assets/MessageKitAssets.bundle/en.lproj/Root.strings create mode 100644 Sources/Bundle+Extensions.swift create mode 100644 Sources/MessageStyle.swift rename Sources/{UIView+AutoLayout.swift => UIView+Extensions.swift} (100%) diff --git a/Assets/Assets.xcassets/Contents.json b/Assets/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c..00000000 --- a/Assets/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json b/Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json deleted file mode 100644 index ce1e2474..00000000 --- a/Assets/Assets.xcassets/bubble_tailed.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "bubble_tailed.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Assets/Info.plist b/Assets/Info.plist deleted file mode 100644 index 750afb3c..00000000 --- a/Assets/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - NSHumanReadableCopyright - Copyright © 2017 MessageKit. All rights reserved. - NSPrincipalClass - - - diff --git a/Assets/Assets.xcassets/bubble_tailed.imageset/bubble_tailed.png b/Assets/MessageKitAssets.bundle/Images/bubble_tailed.png similarity index 100% rename from Assets/Assets.xcassets/bubble_tailed.imageset/bubble_tailed.png rename to Assets/MessageKitAssets.bundle/Images/bubble_tailed.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_tailed@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_tailed@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4081d1070934b6d632b0ca9628f2253d6e6497df GIT binary patch literal 1177 zcmV;K1ZMk*P) z;zsV~F<#|EzNQtu=>3=Bj0rN5-v4MrD?aB_UgZ&Pn{mO32fSSdl}yg*Rx= z6qGj8=*Iip!7;2sDS1!{8*v3+Fb-whWWMJ{wxyh$tcnA8jnOFUOyUEMVJ>o}BDUul z#-pq@l{eUzGP1ncID_seXAI^F<|9j+gDV(?a?T{~WFfMwa?Z0KGp(au&+KGP+tUN( zydfMw7BnxZGWC+SD<2Z~ARm#mIP#$`OO48l}e2MZ_Tb3uStwJA^*G94t zX=^japu9Jky+}(tF&PzvnH-mnIj5k4k>Gez+P2i7f|B4UQr0?*Lj`9hdyq#*YS_3t~?M;O4 zM@?`al5~~L-1q5bu@p(RKWc`%m?V1*HN&+bsY+XX!mL}8B-#|Uz@0)8U4~lV-Xy$$ z=PkM61XsaSA1uHM1hzH`;Pxl5qfr2N9f9450=QQQ>?IVy{X$?rpa5 zImiQF=F?{MoEO8~GI zV|~JUULaN*)qAH0H-TzmwH1?5E$%2{wo{!)MBT5%Zi_nSrEYs-w_2@Ft8qV5MC=x< z^#F-G6oX2+g9Jjjq0Ea(yD${xmC+o=0dzr0dL5(Y;qg2=p1#e(02Fg1y-`w5#jI`f z;)OVaIgHwsaUugyuxV_IS>>FT(HNyA>_8xe!0kh?jNji@3Om9Pp2aWyaUTfK|q1TV81X{eaR z*nmAak;}P_M|qXc_=;9^p!Z(}Gp3fAsGf1P{&wg;-_w~^e9LFN&J*0pm7K<2Y{HV1 rk>09Uo-NpiqdAK!xtW{Tmj! + + + + StringsTable + Root + PreferenceSpecifiers + + + Type + PSGroupSpecifier + Title + Group + + + Type + PSTextFieldSpecifier + Title + Name + Key + name_preference + DefaultValue + + IsSecure + + KeyboardType + Alphabet + AutocapitalizationType + None + AutocorrectionType + No + + + Type + PSToggleSwitchSpecifier + Title + Enabled + Key + enabled_preference + DefaultValue + + + + Type + PSSliderSpecifier + Key + slider_preference + DefaultValue + 0.5 + MinimumValue + 0 + MaximumValue + 1 + MinimumValueImage + + MaximumValueImage + + + + + diff --git a/Assets/MessageKitAssets.bundle/en.lproj/Root.strings b/Assets/MessageKitAssets.bundle/en.lproj/Root.strings new file mode 100644 index 0000000000000000000000000000000000000000..8cd87b9d6b20c1fbf87bd4db3db267fca5ad4df9 GIT binary patch literal 546 zcmaixOHRW;5JYRuDMndFh#Ua1V1d}N;sVAV2TO?uC3a9aJn*VxFrY}tnon0(S66#J z-d9>G>6W!ur(SDqlp`9nn~*(m%iWnv?yq`Qfp6XbK1?+om~~#r)ZnhkYQU_VbfjuT zHNn`CX<0sd*m1A}>&5sU$akD=GTXJ1e literal 0 HcmV?d00001 diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 733da02c..8b1b4709 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -141,13 +141,12 @@ extension ConversationViewController: MessagesDisplayDataSource { } func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { - return .messageTop + return .messageBottom } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { - let lightGray = UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1) - let bubbleBorder = MessageBorder(cornerRadius: 15, color: UIColor.clear, width: 0, fillColor: lightGray) - return .bubble(border: bubbleBorder, withTail: randomTailStyle()) + let corner: UIRectCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft + return .bubbleTail(corner) } func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { @@ -172,23 +171,23 @@ extension ConversationViewController: MessagesDisplayDataSource { } -// MARK: - Helper Methods for random bubble and tail generation +// MARK: - Helper Methods extension ConversationViewController { - fileprivate func randomTailStyle() -> MessageBubbleTailStyle { - let lightGray = UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1) - let tailBorderNoColor = MessageBorder(cornerRadius: 0, color: nil, width: 0, fillColor: lightGray) - let tailBorderColored = MessageBorder(cornerRadius: 0, color: UIColor.lightGray, width: 1, fillColor: UIColor.orange) - let randomStyle = Int(arc4random_uniform(UInt32(MessageBubbleTailStyle.styleCount))) - let isColoredBorder = Int(arc4random_uniform(2)) == 1 - - switch randomStyle { - case 0: return .triangle(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) - case 1: return .tailCurved(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) - default: assert(false); break - } - } - +// fileprivate func randomTailStyle() -> MessageBubbleTailStyle { +// let lightGray = UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1) +// let tailBorderNoColor = MessageBorder(cornerRadius: 0, color: nil, width: 0, fillColor: lightGray) +// let tailBorderColored = MessageBorder(cornerRadius: 0, color: UIColor.lightGray, width: 1, fillColor: UIColor.orange) +// let randomStyle = Int(arc4random_uniform(UInt32(MessageBubbleTailStyle.styleCount))) +// let isColoredBorder = Int(arc4random_uniform(2)) == 1 +// +// switch randomStyle { +// case 0: return .triangle(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) +// case 1: return .tailCurved(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) +// default: assert(false); break +// } +// } +// fileprivate func randomCorner() -> UIRectCorner { var corners: [UIRectCorner] = [.bottomLeft, .bottomRight, .topLeft, .topRight] let random = Int(arc4random_uniform(4)) diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 0366572f..c043ffb2 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376AD1871F4259D20083072A /* TestMessagesViewControllerModel.swift */; }; 37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C936971F38F6AC00853DF2 /* Avatar.swift */; }; 38C8679A1F50EA1000811974 /* NSConstraintLayoutSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */; }; - 38C8679B1F50EA1000811974 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867991F50EA1000811974 /* UIView+AutoLayout.swift */; }; + 38C8679B1F50EA1000811974 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C867991F50EA1000811974 /* UIView+Extensions.swift */; }; 38C8679E1F50EA4A00811974 /* InputBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C8679D1F50EA4A00811974 /* InputBarItem.swift */; }; 882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882D75831DE507320033F95F /* MessagesDataSource.swift */; }; 888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888CEBFB1D3FD525005178DE /* MessagesViewController.swift */; }; @@ -24,10 +24,11 @@ 88916B451CF0DF5900469F91 /* MessageKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88916B431CF0DF5900469F91 /* MessageKitTests.swift */; }; 88916B471CF0DFE600469F91 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88916B461CF0DFE600469F91 /* MessageType.swift */; }; B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */; }; + B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; - B01E2DD41F5BA48300E4FA1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */; }; - B01E2DD51F5BA49100E4FA1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */; }; + B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; + B01E2DDB1F5BC10000E4FA1C /* MessageKitAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */; }; B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */; }; B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05530B41F493CFA008BB420 /* DetectorType.swift */; }; B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A271F23D71400542A83 /* MessageDirection.swift */; }; @@ -57,13 +58,6 @@ remoteGlobalIDString = 88916B211CF0DF2F00469F91; remoteInfo = MessageKit; }; - B01E2DD11F5BA3E900E4FA1C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 88916B191CF0DF2F00469F91 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B01E2DCA1F5BA34700E4FA1C; - remoteInfo = Assets; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -75,7 +69,7 @@ 376AD1871F4259D20083072A /* TestMessagesViewControllerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestMessagesViewControllerModel.swift; sourceTree = ""; }; 37C936971F38F6AC00853DF2 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = ""; }; 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSConstraintLayoutSet.swift; sourceTree = ""; }; - 38C867991F50EA1000811974 /* UIView+AutoLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayout.swift"; sourceTree = ""; }; + 38C867991F50EA1000811974 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; 38C8679D1F50EA4A00811974 /* InputBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputBarItem.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 = ""; }; @@ -87,11 +81,11 @@ 88916B431CF0DF5900469F91 /* MessageKitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitTests.swift; sourceTree = ""; }; 88916B461CF0DFE600469F91 /* MessageType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = ""; }; B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = ""; }; + B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; - B01E2DCB1F5BA34700E4FA1C /* Assets.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Assets.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - B01E2DCD1F5BA34700E4FA1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; + B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = MessageKitAssets.bundle; sourceTree = ""; }; B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellDelegate.swift; sourceTree = ""; }; B05530B41F493CFA008BB420 /* DetectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = ""; }; B0655A271F23D71400542A83 /* MessageDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDirection.swift; sourceTree = ""; }; @@ -129,13 +123,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B01E2DC81F5BA34700E4FA1C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -151,7 +138,6 @@ 88916B181CF0DF2F00469F91 = { isa = PBXGroup; children = ( - B01E2DCC1F5BA34700E4FA1C /* Assets */, 88916B3C1CF0DF5100469F91 /* Sources */, 88916B411CF0DF5900469F91 /* Tests */, 88916B231CF0DF2F00469F91 /* Products */, @@ -163,7 +149,6 @@ children = ( 88916B221CF0DF2F00469F91 /* MessageKit.framework */, 88916B2C1CF0DF2F00469F91 /* MessageKitTests.xctest */, - B01E2DCB1F5BA34700E4FA1C /* Assets.bundle */, ); name = Products; sourceTree = ""; @@ -171,6 +156,7 @@ 88916B3C1CF0DF5100469F91 /* Sources */ = { isa = PBXGroup; children = ( + B01E2DCC1F5BA34700E4FA1C /* Assets */, B096439D1F295DC3004D0129 /* Layout */, B09643991F295D58004D0129 /* Models */, B096439A1F295D68004D0129 /* Views */, @@ -197,20 +183,20 @@ B01E2DCC1F5BA34700E4FA1C /* Assets */ = { isa = PBXGroup; children = ( - B01E2DCD1F5BA34700E4FA1C /* Info.plist */, - B01E2DD31F5BA48300E4FA1C /* Assets.xcassets */, + B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */, ); - path = Assets; + name = Assets; + path = ../Assets; sourceTree = ""; }; B09643981F295D43004D0129 /* Extensions */ = { isa = PBXGroup; children = ( - 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, - 38C867991F50EA1000811974 /* UIView+AutoLayout.swift */, + 38C867991F50EA1000811974 /* UIView+Extensions.swift */, B09643851F286C9E004D0129 /* String+Extensions.swift */, B096438F1F289142004D0129 /* UIColor+Extensions.swift */, B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */, + B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */, ); name = Extensions; sourceTree = ""; @@ -224,6 +210,8 @@ B0655A271F23D71400542A83 /* MessageDirection.swift */, B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */, B05530B41F493CFA008BB420 /* DetectorType.swift */, + B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */, + 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, ); name = Models; sourceTree = ""; @@ -312,7 +300,6 @@ buildRules = ( ); dependencies = ( - B01E2DD21F5BA3E900E4FA1C /* PBXTargetDependency */, ); name = MessageKit; productName = MessageKit; @@ -337,23 +324,6 @@ productReference = 88916B2C1CF0DF2F00469F91 /* MessageKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - B01E2DCA1F5BA34700E4FA1C /* Assets */ = { - isa = PBXNativeTarget; - buildConfigurationList = B01E2DD01F5BA34700E4FA1C /* Build configuration list for PBXNativeTarget "Assets" */; - buildPhases = ( - B01E2DC71F5BA34700E4FA1C /* Sources */, - B01E2DC81F5BA34700E4FA1C /* Frameworks */, - B01E2DC91F5BA34700E4FA1C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Assets; - productName = Assets; - productReference = B01E2DCB1F5BA34700E4FA1C /* Assets.bundle */; - productType = "com.apple.product-type.bundle"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -371,10 +341,6 @@ 88916B2B1CF0DF2F00469F91 = { CreatedOnToolsVersion = 7.3.1; }; - B01E2DCA1F5BA34700E4FA1C = { - CreatedOnToolsVersion = 8.3.2; - ProvisioningStyle = Automatic; - }; }; }; buildConfigurationList = 88916B1C1CF0DF2F00469F91 /* Build configuration list for PBXProject "MessageKit" */; @@ -391,7 +357,6 @@ targets = ( 88916B211CF0DF2F00469F91 /* MessageKit */, 88916B2B1CF0DF2F00469F91 /* MessageKitTests */, - B01E2DCA1F5BA34700E4FA1C /* Assets */, ); }; /* End PBXProject section */ @@ -401,7 +366,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - B01E2DD51F5BA49100E4FA1C /* Assets.xcassets in Resources */, + B01E2DDB1F5BC10000E4FA1C /* MessageKitAssets.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -412,14 +377,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B01E2DC91F5BA34700E4FA1C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B01E2DD41F5BA48300E4FA1C /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -449,6 +406,7 @@ 38C8679A1F50EA1000811974 /* NSConstraintLayoutSet.swift in Sources */, 882D75841DE507320033F95F /* MessagesDataSource.swift in Sources */, 888CEBFC1D3FD525005178DE /* MessagesViewController.swift in Sources */, + B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */, E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */, B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */, B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */, @@ -466,10 +424,11 @@ B074EE951F35588A00ABB8C8 /* MessageFooterView.swift in Sources */, B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */, B0655A381F23EE8B00542A83 /* MessageInputBar.swift in Sources */, - 38C8679B1F50EA1000811974 /* UIView+AutoLayout.swift in Sources */, + 38C8679B1F50EA1000811974 /* UIView+Extensions.swift in Sources */, B074EEA81F3971A600ABB8C8 /* MessageLabel.swift in Sources */, 372F6AEB1F36C15600B57FBD /* AvatarView.swift in Sources */, B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */, + B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */, 88916B471CF0DFE600469F91 /* MessageType.swift in Sources */, B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */, B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */, @@ -489,13 +448,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B01E2DC71F5BA34700E4FA1C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -504,11 +456,6 @@ target = 88916B211CF0DF2F00469F91 /* MessageKit */; targetProxy = 88916B2E1CF0DF2F00469F91 /* PBXContainerItemProxy */; }; - B01E2DD21F5BA3E900E4FA1C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B01E2DCA1F5BA34700E4FA1C /* Assets */; - targetProxy = B01E2DD11F5BA3E900E4FA1C /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -672,40 +619,6 @@ }; name = Release; }; - B01E2DCE1F5BA34700E4FA1C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = NO; - INFOPLIST_FILE = Assets/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - PRODUCT_BUNDLE_IDENTIFIER = MessageKit.Assets; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; - B01E2DCF1F5BA34700E4FA1C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = NO; - INFOPLIST_FILE = Assets/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - PRODUCT_BUNDLE_IDENTIFIER = MessageKit.Assets; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - WRAPPER_EXTENSION = bundle; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -736,14 +649,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B01E2DD01F5BA34700E4FA1C /* Build configuration list for PBXNativeTarget "Assets" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B01E2DCE1F5BA34700E4FA1C /* Debug */, - B01E2DCF1F5BA34700E4FA1C /* Release */, - ); - defaultConfigurationIsVisible = 0; - }; /* End XCConfigurationList section */ }; rootObject = 88916B191CF0DF2F00469F91 /* Project object */; diff --git a/Sources/Bundle+Extensions.swift b/Sources/Bundle+Extensions.swift new file mode 100644 index 00000000..0781dd47 --- /dev/null +++ b/Sources/Bundle+Extensions.swift @@ -0,0 +1,36 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import Foundation + +extension Bundle { + + static func messageKitAssetBundle() -> Bundle { + let bundle = Bundle(for: MessagesViewController.self) + let resourceUrl = bundle.resourceURL!.appendingPathComponent("MessageKitAssets.bundle") + return Bundle(url: resourceUrl)! + } + + +} diff --git a/Sources/MessageContainerView.swift b/Sources/MessageContainerView.swift index bc3910ab..a9a3a7cb 100644 --- a/Sources/MessageContainerView.swift +++ b/Sources/MessageContainerView.swift @@ -26,273 +26,43 @@ import UIKit open class MessageContainerView: UIView { - var style: MessageStyle = .none { + // MARK: - Properties + + open let imageView = UIImageView() + + open var style: MessageStyle = .none { didSet { - // ***** IMPORTANT ******** - // do not check against oldValue and skip updating as the view gets created in cell and it is dequeued, - // it gets created for a limited number of times but gets used many times, - // therefore we need to draw it each time the style is set - // DO NOT DO THIS --> guard style != oldValue else { return } - setNeedsDisplay() + imageView.image = style.image } } - fileprivate var tailHeight: CGFloat! - fileprivate var tailWidth: CGFloat! - - open override func draw(_ rect: CGRect) { - switch style { - case .bubble(let border, let tailStyle): - tailHeight = border.cornerRadius - tailWidth = border.cornerRadius / 2 - drawBubble(in: rect, with: border, using: tailStyle) - case .none: - self.layer.cornerRadius = 0 - self.layer.masksToBounds = false - self.layer.borderWidth = 0 - self.layer.borderColor = nil + open var messageColor: UIColor = .white { + didSet { + imageView.tintColor = messageColor } } - private func drawBubble(in rect: CGRect, with border: MessageBorder, using tailStyle: MessageBubbleTailStyle) { - let tailPath: UIBezierPath - let outlinePath: UIBezierPath - let bubbleTailBorder: MessageBorder? + // MARK: - Initializers - switch tailStyle { - case .none: - tailPath = UIBezierPath() - outlinePath = UIBezierPath(roundedRect: rect, cornerRadius: border.cornerRadius) - bubbleTailBorder = nil - case .triangle(let corner, let tailBorder): - (tailPath, outlinePath) = createTriangleTailedBubble(for: rect, with: border, for: corner) - bubbleTailBorder = tailBorder - case .tailCurved(let corner, let tailBorder): - (tailPath, outlinePath) = createTailCurvedTailedBubble(for: rect, with: border, for: corner) - bubbleTailBorder = tailBorder - } - - border.fillColor.setFill() - outlinePath.fill() - - if let bubbleTailBorder = bubbleTailBorder { - bubbleTailBorder.fillColor.setFill() - tailPath.fill() - if let outlineColor = bubbleTailBorder.color { - outlineColor.setStroke() - tailPath.lineWidth = bubbleTailBorder.width - tailPath.stroke() - } - } else { - tailPath.fill() - } - - if let borderColor = border.color { - borderColor.setStroke() - outlinePath.lineWidth = border.width - outlinePath.stroke() - } + override public init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + setupConstraints() + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - private func createTriangleTailedBubble(for rect: CGRect, with border: MessageBorder, for corner: UIRectCorner) -> (tailPath: UIBezierPath, outlinePath: UIBezierPath) { - let tailPath = UIBezierPath() - switch corner { - case UIRectCorner.bottomLeft: - tailPath.move(to: CGPoint(x: rect.minX, y: rect.maxY)) - tailPath.addLine(to: CGPoint(x: rect.minX, y: rect.maxY-border.cornerRadius)) - tailPath.addLine(to: CGPoint(x: rect.minX+border.cornerRadius, y: rect.maxY)) - case UIRectCorner.bottomRight: - tailPath.move(to: CGPoint(x: rect.maxX, y: rect.maxY)) - tailPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY-border.cornerRadius)) - tailPath.addLine(to: CGPoint(x: rect.maxX-border.cornerRadius, y: rect.maxY)) - case UIRectCorner.topLeft: - tailPath.move(to: CGPoint(x: rect.minX, y: rect.minY)) - tailPath.addLine(to: CGPoint(x: rect.minX, y: rect.minY+border.cornerRadius)) - tailPath.addLine(to: CGPoint(x: rect.minX+border.cornerRadius, y: rect.minY)) - case UIRectCorner.topRight: - tailPath.move(to: CGPoint(x: rect.maxX, y: rect.minY)) - tailPath.addLine(to: CGPoint(x: rect.maxX, y: rect.minY+border.cornerRadius)) - tailPath.addLine(to: CGPoint(x: rect.maxX-border.cornerRadius, y: rect.minY)) - default: - fatalError("FATAL ERROR unhandled UIRectCorner") - } + // MARK: - Methods - tailPath.close() - let outlinePath = UIBezierPath(roundedRect: rect, - byRoundingCorners: [.allCorners], - cornerRadii: CGSize(width: border.cornerRadius, height: 0)) - return (tailPath, outlinePath) + func setupSubviews() { + imageView.isUserInteractionEnabled = true + addSubview(imageView) + } + + func setupConstraints() { + imageView.fillSuperview() } } - -// MARK: - Style 1 related -extension MessageContainerView { - fileprivate func createTailCurvedTailedBubble(for rect: CGRect, with border: MessageBorder, for corner: UIRectCorner) -> (tailPath: UIBezierPath, outlinePath: UIBezierPath) { - let tailPath: UIBezierPath - let insetRect: CGRect - - switch corner { - case UIRectCorner.bottomLeft: - let widthInset = UIEdgeInsets(top: 0, left: tailWidth, bottom: 0, right: 0) - insetRect = UIEdgeInsetsInsetRect(rect, widthInset) - tailPath = tailCurvedTailPathForBottomLeft(using: insetRect) - - case UIRectCorner.bottomRight: - let widthInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: tailWidth) - insetRect = UIEdgeInsetsInsetRect(rect, widthInset) - tailPath = tailCurvedTailPathForBottomLeft(using: insetRect) - tailPath.mirrorOnXAxis(in: insetRect) - - case UIRectCorner.topLeft: - let widthInset = UIEdgeInsets(top: 0, left: tailWidth, bottom: 0, right: 0) - insetRect = UIEdgeInsetsInsetRect(rect, widthInset) - tailPath = tailCurvedTailPathForTopLeft(using: insetRect) - - case UIRectCorner.topRight: - let widthInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: tailWidth) - insetRect = UIEdgeInsetsInsetRect(rect, widthInset) - tailPath = tailCurvedTailPathForTopLeft(using: insetRect) - tailPath.mirrorOnXAxis(in: insetRect) - - default: fatalError("FATAL ERROR unhandled UIRectCorner") - } - - let outlinePath = UIBezierPath(roundedRect: insetRect, - byRoundingCorners: [.allCorners], - cornerRadii: CGSize(width: border.cornerRadius, height: 0)) - return (tailPath, outlinePath) - - } - - private func tailCurvedTailPathForBottomLeft(using insetRect: CGRect) -> UIBezierPath { - let tailPath = UIBezierPath() - let tailStartPoint = CGPoint(x: insetRect.minX, y: insetRect.maxY - tailHeight) - let tailBottomPoint = CGPoint(x: insetRect.minX - tailWidth, y: insetRect.maxY) - let controlPoint1 = CGPoint(x: tailHeight/2, y: insetRect.maxY - tailWidth) - tailPath.move(to: tailStartPoint) - tailPath.addQuadCurve(to: tailBottomPoint, controlPoint: controlPoint1) - let tailMiddlePoint = CGPoint(x: tailWidth * 2, y: insetRect.maxY - ((tailWidth * 2)/3)) - let controlPoint2 = CGPoint(x: tailWidth, y: insetRect.maxY - 1) - tailPath.addQuadCurve(to: tailMiddlePoint, controlPoint: controlPoint2) - tailPath.close() - return tailPath - } - - private func tailCurvedTailPathForTopLeft(using insetRect: CGRect) -> UIBezierPath { - let tailPath = UIBezierPath() - let tailStartPoint = CGPoint(x: insetRect.minX, y: insetRect.minY + tailHeight) - let tailBottomPoint = CGPoint(x: insetRect.minX - tailWidth, y: insetRect.minY) - let controlPoint1 = CGPoint(x: tailHeight/2, y: insetRect.minY + tailWidth) - tailPath.move(to: tailStartPoint) - tailPath.addQuadCurve(to: tailBottomPoint, controlPoint: controlPoint1) - let tailMiddlePoint = CGPoint(x: tailWidth * 2, y: insetRect.minY + ((tailWidth * 2)/3)) - let controlPoint2 = CGPoint(x: tailWidth, y: insetRect.minY + 1) - tailPath.addQuadCurve(to: tailMiddlePoint, controlPoint: controlPoint2) - tailPath.close() - return tailPath - } - -} - -public enum MessageBubbleTailStyle { - case none - case triangle(corner: UIRectCorner, border: MessageBorder) - case tailCurved(corner: UIRectCorner, border: MessageBorder) // we should find a proper name for this - - public static let styleCount = 2 -} - -extension MessageBubbleTailStyle: CustomDebugStringConvertible { - public var name: String { - switch self { - case .none: return "None" - case .triangle: return "Triangle" - case .tailCurved: return "TailCurved" - } - } - - public var debugDescription: String { - switch self { - case .triangle(let corner, let border): - return "name: \(name), corner: \(corner), border: \(border)" - case .tailCurved(let corner): - return "name: \(name), corner: \(corner)" - default: - return "name: \(name)" - } - } -} - -public struct MessageBorder { - let color: UIColor? - let width: CGFloat - let cornerRadius: CGFloat - let fillColor: UIColor - - public init(cornerRadius: CGFloat = 15, color: UIColor? = nil, width: CGFloat = 0, fillColor: UIColor) { - self.color = color - self.width = width - self.cornerRadius = cornerRadius - self.fillColor = fillColor - } -} - -extension MessageBorder: CustomDebugStringConvertible { - public var debugDescription: String { - return "color: \(color.debugDescription), width: \(width), cornerRadius: \(cornerRadius)" - } -} - -public enum MessageStyle { - case none - case bubble(border: MessageBorder, withTail: MessageBubbleTailStyle) -} - -extension MessageStyle { - public var name: String { - switch self { - case .bubble: return "Bubble" - case .none: return "None" - } - } -} - -extension MessageStyle: CustomDebugStringConvertible { - public var debugDescription: String { - switch self { - case .bubble(let border, let tailStyle): - return "MessageStyle: Bubble, border: \(border.debugDescription), tailStyle: \(tailStyle.debugDescription)" - case .none: - return "MessageStyle: none" - } - } -} - -extension MessageStyle: Equatable { - static public func == (lhs: MessageStyle, rhs: MessageStyle) -> Bool { - return lhs.name == rhs.name - } -} - -public extension UIImage { - public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { - let rect = CGRect(origin: .zero, size: size) - UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) - color.setFill() - UIRectFill(rect) - let image = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - - guard let cgImage = image?.cgImage else { return nil } - self.init(cgImage: cgImage) - } -} - -public extension UIBezierPath { - public func mirrorOnXAxis(in rect: CGRect) { - apply(CGAffineTransform(translationX: -rect.origin.x, y: 0)) - apply(CGAffineTransform(scaleX: -1, y: 1)) - apply(CGAffineTransform(translationX: rect.origin.x+rect.width, y: 0)) - } -} diff --git a/Sources/MessageStyle.swift b/Sources/MessageStyle.swift new file mode 100644 index 00000000..83bdf4f0 --- /dev/null +++ b/Sources/MessageStyle.swift @@ -0,0 +1,91 @@ +/* + 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 + +fileprivate extension UIRectCorner { + + var imageOrientation: UIImageOrientation { + switch self { + case UIRectCorner.topLeft: + return .down + case UIRectCorner.bottomLeft: + return .upMirrored + case UIRectCorner.topRight: + return .downMirrored + case UIRectCorner.bottomRight: + return .up + default: + fatalError("Invalid UIRectCorner. Only supports: .topLeft, .bottomLeft, .topRight, .bottomRight") + } + } + +} + +public enum MessageStyle { + + case none + case bubble + case bubbleOutline + case bubbleTail(UIRectCorner) + case bubbleTailOutline(UIRectCorner) + + public var image: UIImage? { + + guard let path = imagePath else { return nil } + + guard var image = UIImage(contentsOfFile: path) else { return nil } + + switch self { + case .none: + return nil + case .bubble, .bubbleOutline: + break + case .bubbleTail(let edge), .bubbleTailOutline(let edge): + guard let cgImage = image.cgImage else { return nil } + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: edge.imageOrientation) + } + + return stretch(image).withRenderingMode(.alwaysTemplate) + } + + private var imageName: String? { + switch self { + default: return "bubble_tailed" + } + } + + private var imagePath: String? { + guard let imageName = imageName else { return nil } + let assetBundle = Bundle.messageKitAssetBundle() + return assetBundle.path(forResource: imageName, ofType: "png", inDirectory: "Images") + } + + private func stretch(_ image: UIImage) -> UIImage { + let center = CGPoint(x: image.size.width / 2, y: image.size.height / 2) + let capInsets = UIEdgeInsets(top: center.y, left: center.x, bottom: center.y, right: center.x) + return image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch) + } + +} diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDataSource.swift index 5706b0c1..be19bab2 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDataSource.swift @@ -53,7 +53,7 @@ public extension MessagesDisplayDataSource { } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { - return .none + return .bubble } func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 0895e075..04008d6f 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -179,10 +179,8 @@ extension MessagesViewController: UICollectionViewDataSource { let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView) if let displayDataSource = messagesDataSource as? MessagesDisplayDataSource { - // MessageContainerView needs to know the color. Currently it doesn't, so we are ignoring the message color here - // And using an arbitrary color in the container view - // As of now, the background color can not be changed! - let messageColor = UIColor.clear + + let messageColor = displayDataSource.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) let messageStyle = displayDataSource.messageStyle(for: message, at: indexPath, in: messagesCollectionView) let textColor = displayDataSource.textColor(for: message, at: indexPath, in: messagesCollectionView) let avatar = displayDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView) @@ -191,7 +189,7 @@ extension MessagesViewController: UICollectionViewDataSource { cell.avatarView.set(avatar: avatar) cell.messageLabel.textColor = textColor - cell.messageContainerView.backgroundColor = messageColor + cell.messageContainerView.messageColor = messageColor cell.messageContainerView.style = messageStyle cell.cellTopLabel.attributedText = topLabelText cell.cellBottomLabel.attributedText = bottomLabelText diff --git a/Sources/UIView+AutoLayout.swift b/Sources/UIView+Extensions.swift similarity index 100% rename from Sources/UIView+AutoLayout.swift rename to Sources/UIView+Extensions.swift From 9968c3540b8777166cfb2dc27e14af660f5a7438 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 3 Sep 2017 05:38:12 -0500 Subject: [PATCH 06/43] Create TailCorner --- .../Sources/ConversationViewController.swift | 2 +- MessageKit.xcodeproj/project.pbxproj | 4 ++ Sources/MessageStyle.swift | 27 ++--------- Sources/TailCorner.swift | 47 +++++++++++++++++++ 4 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 Sources/TailCorner.swift diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 8b1b4709..0220c448 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -145,7 +145,7 @@ extension ConversationViewController: MessagesDisplayDataSource { } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { - let corner: UIRectCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft + let corner: TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft return .bubbleTail(corner) } diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index c043ffb2..45455210 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 88916B471CF0DFE600469F91 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88916B461CF0DFE600469F91 /* MessageType.swift */; }; B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */; }; B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */; }; + B0147C871F5C129C0035B36E /* TailCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C861F5C129C0035B36E /* TailCorner.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; @@ -82,6 +83,7 @@ 88916B461CF0DFE600469F91 /* MessageType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = ""; }; B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = ""; }; B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; + B0147C861F5C129C0035B36E /* TailCorner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TailCorner.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; @@ -211,6 +213,7 @@ B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */, B05530B41F493CFA008BB420 /* DetectorType.swift */, B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */, + B0147C861F5C129C0035B36E /* TailCorner.swift */, 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, ); name = Models; @@ -410,6 +413,7 @@ E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */, B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */, B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */, + B0147C871F5C129C0035B36E /* TailCorner.swift in Sources */, B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */, B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, diff --git a/Sources/MessageStyle.swift b/Sources/MessageStyle.swift index 83bdf4f0..bc78a61b 100644 --- a/Sources/MessageStyle.swift +++ b/Sources/MessageStyle.swift @@ -24,32 +24,13 @@ import UIKit -fileprivate extension UIRectCorner { - - var imageOrientation: UIImageOrientation { - switch self { - case UIRectCorner.topLeft: - return .down - case UIRectCorner.bottomLeft: - return .upMirrored - case UIRectCorner.topRight: - return .downMirrored - case UIRectCorner.bottomRight: - return .up - default: - fatalError("Invalid UIRectCorner. Only supports: .topLeft, .bottomLeft, .topRight, .bottomRight") - } - } - -} - public enum MessageStyle { case none case bubble case bubbleOutline - case bubbleTail(UIRectCorner) - case bubbleTailOutline(UIRectCorner) + case bubbleTail(TailCorner) + case bubbleTailOutline(TailCorner) public var image: UIImage? { @@ -62,9 +43,9 @@ public enum MessageStyle { return nil case .bubble, .bubbleOutline: break - case .bubbleTail(let edge), .bubbleTailOutline(let edge): + case .bubbleTail(let corner), .bubbleTailOutline(let corner): guard let cgImage = image.cgImage else { return nil } - image = UIImage(cgImage: cgImage, scale: image.scale, orientation: edge.imageOrientation) + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: corner.imageOrientation) } return stretch(image).withRenderingMode(.alwaysTemplate) diff --git a/Sources/TailCorner.swift b/Sources/TailCorner.swift new file mode 100644 index 00000000..0c11dc5c --- /dev/null +++ b/Sources/TailCorner.swift @@ -0,0 +1,47 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import UIKit + +public enum TailCorner { + + case topLeft + case bottomLeft + case topRight + case bottomRight + + var imageOrientation: UIImageOrientation { + switch self { + case .bottomRight: + return .up + case .bottomLeft: + return .upMirrored + case .topLeft: + return .down + case .topRight: + return .downMirrored + } + } + +} From a9fa0e244110c7f2d1b55fe1bb7772907dcd794a Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 3 Sep 2017 05:57:30 -0500 Subject: [PATCH 07/43] Handle messageColor for style of .none --- .../Sources/ConversationViewController.swift | 24 ------------------- Sources/MessageContainerView.swift | 14 ++++++++++- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 0220c448..7c432222 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -171,30 +171,6 @@ extension ConversationViewController: MessagesDisplayDataSource { } -// MARK: - Helper Methods - -extension ConversationViewController { -// fileprivate func randomTailStyle() -> MessageBubbleTailStyle { -// let lightGray = UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1) -// let tailBorderNoColor = MessageBorder(cornerRadius: 0, color: nil, width: 0, fillColor: lightGray) -// let tailBorderColored = MessageBorder(cornerRadius: 0, color: UIColor.lightGray, width: 1, fillColor: UIColor.orange) -// let randomStyle = Int(arc4random_uniform(UInt32(MessageBubbleTailStyle.styleCount))) -// let isColoredBorder = Int(arc4random_uniform(2)) == 1 -// -// switch randomStyle { -// case 0: return .triangle(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) -// case 1: return .tailCurved(corner: randomCorner(), border: isColoredBorder ? tailBorderColored : tailBorderNoColor) -// default: assert(false); break -// } -// } -// - fileprivate func randomCorner() -> UIRectCorner { - var corners: [UIRectCorner] = [.bottomLeft, .bottomRight, .topLeft, .topRight] - let random = Int(arc4random_uniform(4)) - return corners[random] - } -} - // MARK: - MessagesLayoutDelegate extension ConversationViewController: MessagesLayoutDelegate { diff --git a/Sources/MessageContainerView.swift b/Sources/MessageContainerView.swift index a9a3a7cb..7a7baaa8 100644 --- a/Sources/MessageContainerView.swift +++ b/Sources/MessageContainerView.swift @@ -33,12 +33,13 @@ open class MessageContainerView: UIView { open var style: MessageStyle = .none { didSet { imageView.image = style.image + updateMessageColor() } } open var messageColor: UIColor = .white { didSet { - imageView.tintColor = messageColor + updateMessageColor() } } @@ -65,4 +66,15 @@ open class MessageContainerView: UIView { imageView.fillSuperview() } + private func updateMessageColor() { + switch style { + case .none: + backgroundColor = messageColor + imageView.tintColor = messageColor + default: + backgroundColor = superview?.backgroundColor + imageView.tintColor = messageColor + } + } + } From 848e048a146a06f195813a0b1d443b63b7dd59d6 Mon Sep 17 00:00:00 2001 From: Nathan Tannar Date: Sun, 3 Sep 2017 12:40:00 -0700 Subject: [PATCH 08/43] A workaround solution for the render issue with MessageInputBar It seems as though when using a UINavigationController there is an issue with the rendering of an inputAccessoryView. This workaround creates a copy during the animation and then deletes it. --- Example/ChatExample.xcodeproj/project.pbxproj | 3 +++ .../Sources/ConversationViewController.swift | 2 +- Sources/InputBarItem.swift | 4 +++- Sources/InputTextView.swift | 9 +++++++- Sources/MessageInputBar.swift | 11 ++++++++-- Sources/MessagesViewController.swift | 21 +++++++++++++++---- 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 4b2018e7..dc7c1915 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -258,6 +258,7 @@ TargetAttributes = { 882B5E321CF7D4B900B6E160 = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 9EUCWD26TP; LastSwiftMigration = 0800; }; 882B5E481CF7D4B900B6E160 = { @@ -507,6 +508,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 9EUCWD26TP; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -521,6 +523,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 9EUCWD26TP; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 8b1b4709..200d23df 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -40,7 +40,7 @@ class ConversationViewController: MessagesViewController { messagesCollectionView.messageLabelDelegate = self messageInputBar.delegate = self - setupInputBar() +// setupInputBar() } func setupInputBar() { diff --git a/Sources/InputBarItem.swift b/Sources/InputBarItem.swift index 9a8168c3..23854aae 100644 --- a/Sources/InputBarItem.swift +++ b/Sources/InputBarItem.swift @@ -135,10 +135,12 @@ open class InputBarButtonItem: UIButton { } required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init(coder: aDecoder) + setup() } open func setup() { + contentVerticalAlignment = .center contentHorizontalAlignment = .center imageView?.contentMode = .scaleAspectFit diff --git a/Sources/InputTextView.swift b/Sources/InputTextView.swift index e5a790b4..44c5ce4b 100644 --- a/Sources/InputTextView.swift +++ b/Sources/InputTextView.swift @@ -37,6 +37,12 @@ open class InputTextView: UITextView { label.translatesAutoresizingMaskIntoConstraints = false return label }() + + open override var text: String! { + didSet { + placeholderLabel.isHidden = !text.isEmpty + } + } private var placeholderLabelConstraintSet: NSLayoutConstraintSet? @@ -84,7 +90,8 @@ open class InputTextView: UITextView { } required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init(coder: aDecoder) + setup() } open func setup() { diff --git a/Sources/MessageInputBar.swift b/Sources/MessageInputBar.swift index 2a820596..b7fa3ac1 100644 --- a/Sources/MessageInputBar.swift +++ b/Sources/MessageInputBar.swift @@ -206,7 +206,8 @@ open class MessageInputBar: UIView { } required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init(coder: aDecoder) + setup() } deinit { @@ -218,7 +219,7 @@ open class MessageInputBar: UIView { open func setup() { backgroundColor = .inputBarGray - autoresizingMask = [.flexibleHeight, .flexibleBottomMargin] + autoresizingMask = [.flexibleHeight] setupSubviews() setupConstraints() setupObservers() @@ -448,5 +449,11 @@ open class MessageInputBar: UIView { delegate?.messageInputBar(self, didPressSendButtonWith: inputTextView.text) textViewDidChange() } +} +extension MessageInputBar { + + func createCopy() -> MessageInputBar? { + return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as? MessageInputBar + } } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 04008d6f..af7c004d 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -30,14 +30,15 @@ open class MessagesViewController: UIViewController { open var messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout()) - open var messageInputBar = MessageInputBar() - + open var messageInputBar = MessageInputBar() + private var messageInputBarCopy: MessageInputBar? + override open var canBecomeFirstResponder: Bool { return true } override open var inputAccessoryView: UIView? { - return messageInputBar + return messageInputBar } open override var shouldAutorotate: Bool { @@ -65,7 +66,6 @@ open class MessagesViewController: UIViewController { // https://stackoverflow.com/questions/31049651/uitextview-as-inputaccessoryview-doesnt-render-text-until-after-animation // Calling this on the root view avoids a side effect where the inputAccessoryView dissapears after tap //view.snapshotView(afterScreenUpdates: true) - } override open func viewDidLayoutSubviews() { @@ -75,6 +75,13 @@ open class MessagesViewController: UIViewController { open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) addKeyboardObservers() + messageInputBarCopy?.removeFromSuperview() + messageInputBarCopy = nil + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setupMessageInputBarCopy() } // MARK: - Initializers @@ -127,6 +134,12 @@ open class MessagesViewController: UIViewController { toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: 0)) } + private func setupMessageInputBarCopy() { + messageInputBarCopy = messageInputBar.createCopy() + guard let copy = messageInputBarCopy else { return } + view.addSubview(copy) + copy.addConstraints(nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0) + } } // MARK: - UICollectionViewDelegate & UICollectionViewDelegateFlowLayout Conformance From e02fc5a298127f5473a9fea1fc77cf3311e42275 Mon Sep 17 00:00:00 2001 From: Nathan Tannar Date: Sun, 3 Sep 2017 12:46:13 -0700 Subject: [PATCH 09/43] Removed myself from signing --- Example/ChatExample.xcodeproj/project.pbxproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index dc7c1915..4ed6401a 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -258,7 +258,6 @@ TargetAttributes = { 882B5E321CF7D4B900B6E160 = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 9EUCWD26TP; LastSwiftMigration = 0800; }; 882B5E481CF7D4B900B6E160 = { @@ -508,7 +507,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 9EUCWD26TP; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -523,7 +522,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 9EUCWD26TP; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; From 35ce223073971c2551b637e1de72c9e161153f8d Mon Sep 17 00:00:00 2001 From: Antonija Pek Date: Mon, 4 Sep 2017 14:20:37 +0300 Subject: [PATCH 10/43] Add images for bubbles --- .../Images/bubble_full_v1.png | Bin 0 -> 538 bytes .../Images/bubble_full_v1@2x.png | Bin 0 -> 1173 bytes .../Images/bubble_full_v1@3x.png | Bin 0 -> 1868 bytes .../Images/bubble_full_v2.png | Bin 0 -> 634 bytes .../Images/bubble_full_v2@2x.png | Bin 0 -> 1381 bytes .../Images/bubble_full_v2@3x.png | Bin 0 -> 2137 bytes .../Images/bubble_full_v3.png | Bin 0 -> 455 bytes .../Images/bubble_full_v3@2x.png | Bin 0 -> 1104 bytes .../Images/bubble_full_v3@3x.png | Bin 0 -> 1902 bytes .../Images/bubble_outlines_v1.png | Bin 0 -> 732 bytes .../Images/bubble_outlines_v1@2x.png | Bin 0 -> 1696 bytes .../Images/bubble_outlines_v1@3x.png | Bin 0 -> 2716 bytes .../Images/bubble_outlines_v2.png | Bin 0 -> 876 bytes .../Images/bubble_outlines_v2@2x.png | Bin 0 -> 1956 bytes .../Images/bubble_outlines_v2@3x.png | Bin 0 -> 3278 bytes .../Images/bubble_outlines_v3.png | Bin 0 -> 721 bytes .../Images/bubble_outlines_v3@2x.png | Bin 0 -> 1640 bytes .../Images/bubble_outlines_v3@3x.png | Bin 0 -> 2836 bytes 18 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v1.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v1@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v1@3x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v2.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v2@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v2@3x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v3.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v3@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_full_v3@3x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1@3x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@3x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v1.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_v1.png new file mode 100644 index 0000000000000000000000000000000000000000..d5023848b606b675abbd3912867f8c764d4cc559 GIT binary patch literal 538 zcmV+#0_FXQP)Px$)Ja4^R9Fe!nLjTCK^VpzAre#~LbHWdB76ZIL7^1!5p;Y5UqY#ot1dJmh(xau zH3|w15(PPl^XGYz-DL7Qb9;Ncd+)q&@?`$*%=~8d_GaFx^t5L~sJ;j-LsQUXtYL(m z+6NkVjP-!<3_5~dph9gHtsg*j=%++A(ANdT3C-Oiw73s_LuEoW>+2k2n1gb$CA9Ji zHS4Hbd7l{1W~X%9Xy8j3m^-1n7+Zf9NP@WyX^ro-3&&Sc^NPud-bxD?p`y0mn1qpO zF2sBqKzBq0Mn~-%!K16*n+J{2*Po9Z^8a>5RS(d3pNUf0T;v)9JwYWTpkBax6N+GN8HZ2 za&K;ir8wbuv^Qzy;qFW>8=$yy$Clwv0Y|r*IpfN`Lnr3Q07N+24#b_hDNEpZnys25 z6EG3dq4`kcxp3NLN>+|}_s}{-cY_J*6BM^d^8>RW@_(I-BrLqXMwA}LefMnTPD!RB5{hDSopL cCy#jT7c%kCWk~@}GXMYp07*qoM6N<$g7BL0mH+?% literal 0 HcmV?d00001 diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v1@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_v1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d490194dc9f587e796c7a4eb48c3a801973cf7 GIT binary patch literal 1173 zcmV;G1Zw+Px(Pf0{URCodHT}f*cK^UF5MkFYR8Wcf^m_cs}62VgrB4RE^)Qbl_=*ffl2XavY zo+ZIkPKu%;C@v_1AR=zK9TXQ(ui_TL74{r|5}_q_nmG(iO0 zsxli^zeB!;9$S2Nj-81i;2Tg+Udxrr1?aL2@G5%1<}M(E;x+gLjkfvX+#raMkW5Rj z!3gwf>E#Z)z`V|pY1uWn0^NAcT6hejL>bnc25esD7wMc#YtFFf4*w9w%_+h>o*ttG zYrc6su0zvKm*%k}qBgC0qZ;k(Cz~;DYRty$^7C56>7Ef&e!H()g$lk0Zs%E1c3pLy z=H^I?9(~`T^O`)38$xE$0yOVVk0;PN^9|}or%K*?JoZ1CVH-tu1r}fvJ}SDIebd@4 zbi>%)(PcYx(YVF6He_4kqZwP~`oCLkj%2f{Hnom6x8}s{{9j>3cdZ@vrDz++C)bDV zNwvnY0;Yq)dzy(cC6Z!KWL~qaQD7H}ot~y>V;mp1Z%?y<(l}LM9&+E5rUYZ$021?6 zIYsLg*qMfcF>Z63#7a|$0vl0E;_1>D*Ix)F6_Q4Q*+{fDDItyHosuO<3AMgZ1riS= z8rN4SB^8!NfkaPeTs2KjN>iu;y-B6fxWq(BnP`3P3M@sAv2R4vIJR*LzZBpdt=N}o zX`HU#{4&k3iS>oXwd7qt!%kDH0Uu7qy}}sBFC$L`#JluVjd2v> zXzA4mNPwtu_rd#8K-^1BHEJA%I9O^m0;+tyaW}wSM_m90)*DC4`v7)U6do(k$T&VN z81|U6s6l~7#!;9~DG$2p1HldodsEPx{qDT+~`6UFcoxZ{nAOKTjr zrFrbEYydg{g?j~7pa}kg$fG=p8bl@Px+21!IgRCodHonMF*RUF6NtX0!3H2f18yKLst20;kLJJn!ABqZM+9XN)n*S$SyYxO5s6WDqY^IWy<` zgYVAFojGTI=QH1X=FXjScY0>jBNigfp_rCqp0D*K)Qjb&#D8eWIp}OmXE5J_j>Pm6 z=Kp~j6Fr``WDt3cPiqX*C7$N#Ls}P+!~tl}v_8Z1JftXrz)IAy18PC+B_ulre}mm# zXd^TqQWz2#!1h}qo{nU~@)^*eZ$S@1*FcI61Xf@jVO@q~#_ljU2G-9&b0I~HzzF6i zAsNvfgOlLTv(xoZ(7~(a-H^=a4};S<9#2Cvp`c<8S~~$3HjBYQ9Mi>hH0BnWvC(hFA8~wdsf#f=m}9#P*=R#XXTIY& z&UeW~(3>g{qSD5lR5tE(I=kUR-s*GuJGrk{5a8SjyRVAZ(YeQfuGUYAF530yGuPJ%!kO?fSA_>)5 zO9FQx>+hh$uCmv?Y@M=`r*JNF_#1yuRchq<;TT8zRMJhC+7eafiojRc@oMN&SqWsF zzBwN9eW>N z*F>fq#3uden`w;*SP>YvDHWO0s<6!tRVF9`qc*LTDQ&2!OkM)~moK;H-8kAVGUZtG zf~F!+G67?6(Ug2hQfUJ;OKy;=t3QD|Oq)Pc4w)9x_99?Fz}R0hr2$=SYDGY%w8ExB zRTALEj?v>$*i-7(VwHzPb(4TmZ%I=w5;qa3m;f)Pa|canJ;W&YUbJE81o}(>(3D06 zOe)v5A}~$B$YV04eA5jAGG#X?Yad1gj6Nc=r|dPVUK@*mIe}RwMQBQe2zW(6ru6D? zC>au(0hu!B!|!L~WlBGfOVC--l)TV69yA3t-YWt$C1dZ@M1MuV zJ%PxSd)%j@3`AgxfJ`YTct;>Ip$}aqrG1A@pq(j6DCcqRUGuyoXLCj_!gc^+A9a*~;L8Z3b@wDv}~ry#?#w=gXu+R+@h?Cgh_x8qJIrKlc)JMrewYQpc|l- zTu_KW1_3@DVi@9h-OR8xB`fAgq6LtQNg9eQ0?V+$Z_vT44a-=+1(k5Lx+Df!9ykr3 zEX^`qk)ATDo2T;Mp>>eyg;P|AreP4^1+Lq;;8lg&>Cnyq!585f#rCC|HJCrdl$(z+*9wIGbI8J@10}`7`;>(QUrfE2SJa!pn^yp!-+cfZg|FKH0000Px%G)Y83R9Fe+naeH&K@^4!84=g8upluLG7%dh2ohTa4`5^K2|UZhQp9B|LR=Pj z01Jtg2pdFL68HQ6OP{1tQLWpUo~oXcd{fokXS&Ycr@FeUI#CkNs^C5fCO|7_@~B3c zi{0SQmmbF`*T61#29a1dT3G>);4>m_(ar+MfV^26XI=y!ASz*cJ1=NE3mU<{+8EBp zuA?_tsBL!P{{K=qgA=`5Bu*|WZ;`27MN*uc(AIMDuOitkZOoj0p&t-uPgs#D{K2Ms zP8X$bUm)JP-$a%v4jifalx^FqwO)WbK?3D-u8@xcJv%|S*;SkU1WE>H;nd;hXz8&e z;v$=V@$vPG!*9^5W}6@H#e_*UVM(>=+N zatL$+ic!T$J@m4E8Yfx;gPx)A4x<(RCodHUE7NfQ5?5~(6Ux8xwWjd6eT=tNhq}sQpn}WlTwnG{SSx-9>v4T z%NFI57g7`nxg=XiF3U=`uyVgHxqUv&*{10?JLjDJotZhm`F`uOGdsU?&V0}3Gv}N+ z=ggd`8jzkf;8aaOV}Kdh)&SK3hG3heeZ$E=1$d2Z2k;QM4_pDdfkN3(` zJjY%5zZX~nQ~?!h3y`x`;2q$*XmS2OMc#G-Q-O-H#mLDSATHCDc>D+Qdk|oA;H}a5 zq6rth7=MC;{Q%o^Z;Wl+cAy7{W~iwD^G0VQkbA3#hwVU7g-XizQE)RduPXA+@`H$% zc=0|0NAvO~VhbGlE#f7Lr%EXMmCN%Y;%*U6TR0^kx8H#cS&Jy*^{>7E1p(J&B_gZM z@D(Ndc6{(|eQDYv@`gp)JUhWkUFf{|<5+Mi#;$_gZI(|wCFmXSI;>m@4vI;Lopg?_ z)W!TRhwT{{kEr8-Q}#(D$K(i$x?*JOhKuH88InVknik>MCZ|qV3dP(p@EA$Y0ocv3 zwo+B-VWc+8Iz@@7%RnsxayH|VZUmihoDz~MGd&JIg748f!o-D%mcRoD==I!ITt;(x zoXyl%;G(U6;Wm&n={Ic?l6ck(yhTjT?0>OIVPGiI=Fq^5-vUo2Y&Ug7i;0QSSRjU}*2JG2CJ$p8nqrp)*649R@enf&xnC-Jv3eNgHcH0r`HOSUwmq)~9Ggdh-TZ^Y4 zSOUd7W`F{6T3;+53>b~V&}9n97cj&;Wk3YAPyak>ZoTVF9H- z=#z4RuS!6^qjtM7@!_=O->glAk*jsHUY*D+Ay-1H3M91 zLffLtj6@^e!vQ|@)Exam{&&Uz-xEAp?$l+$*YpEM+gpnn>tm5@T{Ts0D}Q#tQJoICi#Dc@J>G-!iIJS&|~PBNhW*?+YK)Ix?|sp z-?_M*ZobYTtn;_W8gLR0fa^L{iQ)Ky&^}-+kYuwFz@$n&g7D$eoRq%x?_6t?^NE!x zP+XoU+#!G$v8k;_1U{1+mv;F0HH4dDdIH<<3lXsusN@eoK1cp`0uzD$7`t(+fYZP) zAS_yRpPz+g73{o9#X0^N+sJi58_)$r9rEq}4&-4kz?!D7AxQjeWh`M^0N(7dGatv# zlOWs}Y*YOfEUTF&pfLcK)TjZf1Eh9k;e4fxH)EdxF1-2zVE?TXxDBw);t*F_#tWnl nm>FOaww&xGSTN}08{qg~pB-|Ttm&$Q00000NkvXXu0mjf7SM&? literal 0 HcmV?d00001 diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v2@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_v2@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..afd826d09b40052ae1d52b2882702d258b82c068 GIT binary patch literal 2137 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 literal 0 HcmV?d00001 diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v3@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_v3@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..22a7c7a026aa981e0802ea42c4160f4d341c83ee GIT binary patch literal 1104 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 O0000HQ00001b5ch_0Itp) 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 literal 0 HcmV?d00001 diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7924f642eacf8a546cfcd4a9ed5001c41ef5b0d9 GIT binary patch literal 3278 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 literal 0 HcmV?d00001 From 4a3a5e0a233fdcd98fe0400c0c26d0323911a23f Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Mon, 4 Sep 2017 08:06:12 -0500 Subject: [PATCH 11/43] Remove old image assets and rename images --- .../{bubble_full_v3.png => bubble_full.png} | Bin ...{bubble_full_v3@2x.png => bubble_full@2x.png} | Bin ...{bubble_full_v3@3x.png => bubble_full@3x.png} | Bin ...ubble_full_v1.png => bubble_full_tail_v1.png} | Bin ...full_v1@2x.png => bubble_full_tail_v1@2x.png} | Bin ...full_v1@3x.png => bubble_full_tail_v1@3x.png} | Bin ...ubble_full_v2.png => bubble_full_tail_v2.png} | Bin ...full_v2@2x.png => bubble_full_tail_v2@2x.png} | Bin ...full_v2@3x.png => bubble_full_tail_v2@3x.png} | Bin ...ubble_outlines_v3.png => bubble_outlined.png} | Bin ...outlines_v3@2x.png => bubble_outlined@2x.png} | Bin ...outlines_v3@3x.png => bubble_outlined@3x.png} | Bin ...tlines_v1.png => bubble_outlined_tail_v1.png} | Bin ..._v1@2x.png => bubble_outlined_tail_v1@2x.png} | Bin ..._v1@3x.png => bubble_outlined_tail_v1@3x.png} | Bin ...tlines_v2.png => bubble_outlined_tail_v2.png} | Bin ..._v2@2x.png => bubble_outlined_tail_v2@2x.png} | Bin ..._v2@3x.png => bubble_outlined_tail_v2@3x.png} | Bin .../Images/bubble_tailed.png | Bin 380 -> 0 bytes .../Images/bubble_tailed@3x.png | Bin 1177 -> 0 bytes 20 files changed, 0 insertions(+), 0 deletions(-) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v3.png => bubble_full.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v3@2x.png => bubble_full@2x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v3@3x.png => bubble_full@3x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v1.png => bubble_full_tail_v1.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v1@2x.png => bubble_full_tail_v1@2x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v1@3x.png => bubble_full_tail_v1@3x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v2.png => bubble_full_tail_v2.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v2@2x.png => bubble_full_tail_v2@2x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_full_v2@3x.png => bubble_full_tail_v2@3x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v3.png => bubble_outlined.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v3@2x.png => bubble_outlined@2x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v3@3x.png => bubble_outlined@3x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v1.png => bubble_outlined_tail_v1.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v1@2x.png => bubble_outlined_tail_v1@2x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v1@3x.png => bubble_outlined_tail_v1@3x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v2.png => bubble_outlined_tail_v2.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v2@2x.png => bubble_outlined_tail_v2@2x.png} (100%) rename Assets/MessageKitAssets.bundle/Images/{bubble_outlines_v2@3x.png => bubble_outlined_tail_v2@3x.png} (100%) delete mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_tailed.png delete mode 100644 Assets/MessageKitAssets.bundle/Images/bubble_tailed@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v3.png b/Assets/MessageKitAssets.bundle/Images/bubble_full.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v3.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v3@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full@2x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v3@2x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full@2x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v3@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full@3x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v3@3x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v1.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v1.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v1@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@2x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v1@2x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@2x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v1@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@3x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v1@3x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v1@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v2.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v2.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v2@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@2x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v2@2x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@2x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_full_v2@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@3x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_full_v2@3x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_full_tail_v2@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined@2x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3@2x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined@2x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined@3x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v3@3x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@2x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1@2x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@2x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@3x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v1@3x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v1@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@2x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@2x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@2x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@2x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@3x.png b/Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@3x.png similarity index 100% rename from Assets/MessageKitAssets.bundle/Images/bubble_outlines_v2@3x.png rename to Assets/MessageKitAssets.bundle/Images/bubble_outlined_tail_v2@3x.png diff --git a/Assets/MessageKitAssets.bundle/Images/bubble_tailed.png b/Assets/MessageKitAssets.bundle/Images/bubble_tailed.png deleted file mode 100644 index f4e7f93d139361d5a31b0440f385439ab6aebbf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 380 zcmV-?0fYXDP)u`7PaCe869+wI!afjd#_nRv6T+hx_t5fa0d$33# zdMX)Vo>lfa;f!}io^irHtIRV(C3*sIlBZ;_jB>&y(s_bI=qTa?$_3w(kKKf^jB&*1>qKgAe(xZ4kP(4*CfPuepm6Euz8CJi>@uoQNiaFd)|fAX96Ckf&_~b@GD{|q@Pu4 a@ca!u+4^?6ZMG`_0000 z;zsV~F<#|EzNQtu=>3=Bj0rN5-v4MrD?aB_UgZ&Pn{mO32fSSdl}yg*Rx= z6qGj8=*Iip!7;2sDS1!{8*v3+Fb-whWWMJ{wxyh$tcnA8jnOFUOyUEMVJ>o}BDUul z#-pq@l{eUzGP1ncID_seXAI^F<|9j+gDV(?a?T{~WFfMwa?Z0KGp(au&+KGP+tUN( zydfMw7BnxZGWC+SD<2Z~ARm#mIP#$`OO48l}e2MZ_Tb3uStwJA^*G94t zX=^japu9Jky+}(tF&PzvnH-mnIj5k4k>Gez+P2i7f|B4UQr0?*Lj`9hdyq#*YS_3t~?M;O4 zM@?`al5~~L-1q5bu@p(RKWc`%m?V1*HN&+bsY+XX!mL}8B-#|Uz@0)8U4~lV-Xy$$ z=PkM61XsaSA1uHM1hzH`;Pxl5qfr2N9f9450=QQQ>?IVy{X$?rpa5 zImiQF=F?{MoEO8~GI zV|~JUULaN*)qAH0H-TzmwH1?5E$%2{wo{!)MBT5%Zi_nSrEYs-w_2@Ft8qV5MC=x< z^#F-G6oX2+g9Jjjq0Ea(yD${xmC+o=0dzr0dL5(Y;qg2=p1#e(02Fg1y-`w5#jI`f z;)OVaIgHwsaUugyuxV_IS>>FT(HNyA>_8xe!0kh?jNji@3Om9Pp2aWyaUTfK|q1TV81X{eaR z*nmAak;}P_M|qXc_=;9^p!Z(}Gp3fAsGf1P{&wg;-_w~^e9LFN&J*0pm7K<2Y{HV1 rk>09Uo-NpiqdAK!xtW{Tmj! Date: Mon, 4 Sep 2017 08:39:28 -0500 Subject: [PATCH 12/43] Add new message styles --- .../Sources/ConversationViewController.swift | 4 +- Sources/MessageStyle.swift | 60 +++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 7c432222..bff55bed 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -145,8 +145,8 @@ extension ConversationViewController: MessagesDisplayDataSource { } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { - let corner: TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft - return .bubbleTail(corner) + let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft + return .bubbleTail(corner, .pointedEdge) } func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { diff --git a/Sources/MessageStyle.swift b/Sources/MessageStyle.swift index bc78a61b..82ec17c5 100644 --- a/Sources/MessageStyle.swift +++ b/Sources/MessageStyle.swift @@ -26,11 +26,52 @@ import UIKit public enum MessageStyle { + // MARK: - TailCorner + + public enum TailCorner { + + case topLeft + case bottomLeft + case topRight + case bottomRight + + var imageOrientation: UIImageOrientation { + switch self { + case .bottomRight: return .up + case .bottomLeft: return .upMirrored + case .topLeft: return .down + case .topRight: return .downMirrored + } + } + } + + // MARK: - TailStyle + + public enum TailStyle { + + case curved + case pointedEdge + + var imageNameSuffix: String { + switch self { + case .curved: + return "_tail_v2" + case .pointedEdge: + return "_tail_v1" + } + } + } + + // MARK: - MessageStyle + case none case bubble case bubbleOutline - case bubbleTail(TailCorner) - case bubbleTailOutline(TailCorner) + case bubbleTail(TailCorner, TailStyle) + case bubbleTailOutline(TailCorner, TailStyle) + + + // MARK: - Public public var image: UIImage? { @@ -43,7 +84,7 @@ public enum MessageStyle { return nil case .bubble, .bubbleOutline: break - case .bubbleTail(let corner), .bubbleTailOutline(let corner): + case .bubbleTail(let corner, _), .bubbleTailOutline(let corner, _): guard let cgImage = image.cgImage else { return nil } image = UIImage(cgImage: cgImage, scale: image.scale, orientation: corner.imageOrientation) } @@ -51,9 +92,20 @@ public enum MessageStyle { return stretch(image).withRenderingMode(.alwaysTemplate) } + // MARK: - Private + private var imageName: String? { switch self { - default: return "bubble_tailed" + case .bubble: + return "bubble_full" + case .bubbleOutline: + return "bubble_outlined" + case .bubbleTail(_, let tailStyle): + return "bubble_full" + tailStyle.imageNameSuffix + case .bubbleTailOutline(_, let tailStyle): + return "bubble_outlined" + tailStyle.imageNameSuffix + case .none: + return nil } } From 50179c403f4f8a6f6c5b4b61d708fb93b3f74b36 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Mon, 4 Sep 2017 09:06:43 -0500 Subject: [PATCH 13/43] Gardening for MessageInputBar rendering bugfix --- .../Sources/ConversationViewController.swift | 3 +- Sources/Bundle+Extensions.swift | 1 - Sources/MessagesViewController.swift | 29 +++++++++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 200d23df..39b8fd96 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -39,8 +39,7 @@ class ConversationViewController: MessagesViewController { messagesCollectionView.messageCellDelegate = self messagesCollectionView.messageLabelDelegate = self messageInputBar.delegate = self - -// setupInputBar() + //setupInputBar() } func setupInputBar() { diff --git a/Sources/Bundle+Extensions.swift b/Sources/Bundle+Extensions.swift index 0781dd47..550d992d 100644 --- a/Sources/Bundle+Extensions.swift +++ b/Sources/Bundle+Extensions.swift @@ -32,5 +32,4 @@ extension Bundle { return Bundle(url: resourceUrl)! } - } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index af7c004d..4578e97a 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -31,6 +31,7 @@ open class MessagesViewController: UIViewController { open var messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout()) open var messageInputBar = MessageInputBar() + private var messageInputBarCopy: MessageInputBar? override open var canBecomeFirstResponder: Bool { @@ -62,26 +63,22 @@ open class MessagesViewController: UIViewController { registerReusableFooters() setupDelegates() - - // https://stackoverflow.com/questions/31049651/uitextview-as-inputaccessoryview-doesnt-render-text-until-after-animation - // Calling this on the root view avoids a side effect where the inputAccessoryView dissapears after tap - //view.snapshotView(afterScreenUpdates: true) + } - override open func viewDidLayoutSubviews() { - messagesCollectionView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: messageInputBar.frame.height, right: 0) + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setupMessageInputBarCopy() } open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) addKeyboardObservers() - messageInputBarCopy?.removeFromSuperview() - messageInputBarCopy = nil + removeMessageInputBarCopy() } - - open override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupMessageInputBarCopy() + + override open func viewDidLayoutSubviews() { + messagesCollectionView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: messageInputBar.frame.height, right: 0) } // MARK: - Initializers @@ -134,12 +131,20 @@ open class MessagesViewController: UIViewController { toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: 0)) } + // MARK: - MessageInputBar + // Fixes bug where MessageInputBar text renders after viewDidAppear + private func setupMessageInputBarCopy() { messageInputBarCopy = messageInputBar.createCopy() guard let copy = messageInputBarCopy else { return } view.addSubview(copy) copy.addConstraints(nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0) } + + private func removeMessageInputBarCopy() { + messageInputBarCopy?.removeFromSuperview() + messageInputBarCopy = nil + } } // MARK: - UICollectionViewDelegate & UICollectionViewDelegateFlowLayout Conformance From 60673db226cf32f90aced0b7fd98d5c146abda67 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:16:25 -0500 Subject: [PATCH 14/43] Create MessageKitDateFormatter --- Sources/MessageKitDateFormatter.swift | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Sources/MessageKitDateFormatter.swift diff --git a/Sources/MessageKitDateFormatter.swift b/Sources/MessageKitDateFormatter.swift new file mode 100644 index 00000000..e111252e --- /dev/null +++ b/Sources/MessageKitDateFormatter.swift @@ -0,0 +1,66 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import Foundation + +open class MessageKitDateFormatter { + + // MARK: - Properties + + static let shared = MessageKitDateFormatter() + + private let formatter = DateFormatter() + + // MARK: - Initializer + + private init() {} + + // MARK: - Methods + + public func string(from date: Date) -> String { + configureDateFormatter(for: date) + return formatter.string(from: date) + } + + public func attributedString(from date: Date, with attributes: [String: Any]) -> NSAttributedString { + let dateString = string(from: date) + return NSAttributedString(string: dateString, attributes: attributes) + } + + open func configureDateFormatter(for date: Date) { + switch true { + case Calendar.current.isDateInToday(date) || Calendar.current.isDateInYesterday(date): + formatter.doesRelativeDateFormatting = true + formatter.dateStyle = .short + formatter.timeStyle = .short + case Calendar.current.isDate(date, equalTo: Date(), toGranularity: .weekOfYear): + formatter.dateFormat = "EEEE h:mm a" + case Calendar.current.isDate(date, equalTo: Date(), toGranularity: .year): + formatter.dateFormat = "E, d MMM, h:mm a" + default: + formatter.dateFormat = "MMM d, yyyy, h:mm a" + } + } + +} From dc2ffbd13488ab76c448500d2854b701a1f20a3e Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:17:10 -0500 Subject: [PATCH 15/43] Create MessageDateHeader --- Sources/MessageDateHeader.swift | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Sources/MessageDateHeader.swift diff --git a/Sources/MessageDateHeader.swift b/Sources/MessageDateHeader.swift new file mode 100644 index 00000000..1455e46a --- /dev/null +++ b/Sources/MessageDateHeader.swift @@ -0,0 +1,48 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import UIKit + +open class MessageDateHeader: MessageHeaderView { + + // MARK: - Properties + + open let dateLabel = UILabel() + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + addSubview(dateLabel) + dateLabel.fillSuperview() + dateLabel.textAlignment = .center + dateLabel.font = .boldSystemFont(ofSize: 10) + dateLabel.textColor = .darkGray + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} From 8468c9babab7bcb9cbf9bceaed7717b0721a3877 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:19:57 -0500 Subject: [PATCH 16/43] Remove TailCorner as its now a MessageStyle nested type --- Sources/TailCorner.swift | 47 ---------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 Sources/TailCorner.swift diff --git a/Sources/TailCorner.swift b/Sources/TailCorner.swift deleted file mode 100644 index 0c11dc5c..00000000 --- a/Sources/TailCorner.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - MIT License - - Copyright (c) 2017 MessageKit - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -import UIKit - -public enum TailCorner { - - case topLeft - case bottomLeft - case topRight - case bottomRight - - var imageOrientation: UIImageOrientation { - switch self { - case .bottomRight: - return .up - case .bottomLeft: - return .upMirrored - case .topLeft: - return .down - case .topRight: - return .downMirrored - } - } - -} From 35f73cd2e3db159cc8d07c77c828867d179c268f Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:28:31 -0500 Subject: [PATCH 17/43] Add default DateHeader implementation and shouldDisplayHeader method --- Sources/MessagesCollectionView.swift | 2 ++ Sources/MessagesDisplayDataSource.swift | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/MessagesCollectionView.swift b/Sources/MessagesCollectionView.swift index e5b8ac1f..f584b925 100644 --- a/Sources/MessagesCollectionView.swift +++ b/Sources/MessagesCollectionView.swift @@ -36,6 +36,8 @@ open class MessagesCollectionView: UICollectionView { open weak var messageLabelDelegate: MessageLabelDelegate? + open var showsDateHeaderAfterTimeInterval: TimeInterval = 3600 + private var indexPathForLastItem: IndexPath? { let lastSection = numberOfSections > 0 ? numberOfSections - 1 : 0 diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDataSource.swift index be19bab2..348b8cee 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDataSource.swift @@ -37,6 +37,8 @@ public protocol MessagesDisplayDataSource: class, MessagesDataSource { func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? + + func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? @@ -69,9 +71,20 @@ public extension MessagesDisplayDataSource { } func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { - return nil + let header = messagesCollectionView.dequeueMessageHeaderView(withReuseIdentifier: "MessageDateHeader", for: indexPath) as? MessageDateHeader + header?.dateLabel.text = MessageKitDateFormatter.shared.string(from: message.sentDate) + return header } - + + func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool { + if indexPath.section == 0 { return false } + let previousSection = indexPath.section - 1 + let previousIndexPath = IndexPath(item: 0, section: previousSection) + let previousMessage = messageForItem(at: previousIndexPath, in: messagesCollectionView) + let timeIntervalSinceLastMessage = message.sentDate.timeIntervalSince(previousMessage.sentDate) + return timeIntervalSinceLastMessage >= messagesCollectionView.showsDateHeaderAfterTimeInterval + } + func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? { return nil } From eefab1b4e24bc7a667cdf91a6b3826ef47c75813 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:35:37 -0500 Subject: [PATCH 18/43] Make default implementations public --- Sources/MessageCellDelegate.swift | 2 +- Sources/MessageLabelDelegate.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MessageCellDelegate.swift b/Sources/MessageCellDelegate.swift index b09ee895..e1330eea 100644 --- a/Sources/MessageCellDelegate.swift +++ b/Sources/MessageCellDelegate.swift @@ -36,7 +36,7 @@ public protocol MessageCellDelegate: class { } -extension MessageCellDelegate { +public extension MessageCellDelegate { func didTapMessage(in cell: MessageCollectionViewCell) {} diff --git a/Sources/MessageLabelDelegate.swift b/Sources/MessageLabelDelegate.swift index 02957158..9640032f 100644 --- a/Sources/MessageLabelDelegate.swift +++ b/Sources/MessageLabelDelegate.swift @@ -36,7 +36,7 @@ public protocol MessageLabelDelegate: class { } -extension MessageLabelDelegate { +public extension MessageLabelDelegate { func didSelectAddress(_ addressComponents: [String: String]) {} From 248744dcc003d0c7b1a820562b194859f528b852 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:36:11 -0500 Subject: [PATCH 19/43] Add default header size implementation for MessageDateHeader --- Sources/MessagesLayoutDelegate.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index b165f19e..ee847129 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -1,18 +1,18 @@ /* MIT License - + Copyright (c) 2017 MessageKit - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -32,10 +32,12 @@ public protocol MessagesLayoutDelegate: class { } -extension MessagesLayoutDelegate { +public extension MessagesLayoutDelegate { func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { - return .zero + guard let dataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return .zero } + let shouldDisplay = dataSource.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) + return shouldDisplay ? CGSize(width: messagesCollectionView.bounds.width, height: 12) : .zero } func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { From 6ef0638efbdd58c5c042a202bee905fba3d16786 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 05:52:25 -0500 Subject: [PATCH 20/43] Make reusable view registration private --- .../Sources/ConversationViewController.swift | 13 ++----- Example/Sources/SampleData.swift | 39 +++++++++++++------ MessageKit.xcodeproj/project.pbxproj | 12 ++++-- ...ader.swift => MessageDateHeaderView.swift} | 4 +- Sources/MessageFooterView.swift | 4 +- Sources/MessageHeaderView.swift | 4 ++ Sources/MessagesCollectionView.swift | 4 +- Sources/MessagesDisplayDataSource.swift | 2 +- Sources/MessagesViewController.swift | 23 +++++------ 9 files changed, 60 insertions(+), 45 deletions(-) rename Sources/{MessageDateHeader.swift => MessageDateHeaderView.swift} (95%) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 61850a12..1a8448c5 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -145,11 +145,7 @@ extension ConversationViewController: MessagesDisplayDataSource { func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft - return .bubbleTail(corner, .pointedEdge) - } - - func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { - return messagesCollectionView.dequeueMessageHeaderView(for: indexPath) + return .bubbleTail(corner, .curved) } func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? { @@ -174,12 +170,9 @@ extension ConversationViewController: MessagesDisplayDataSource { extension ConversationViewController: MessagesLayoutDelegate { - func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { - return CGSize(width: messagesCollectionView.bounds.width, height: 4) - } - func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { - return CGSize(width: messagesCollectionView.bounds.width, height: 4) + + return CGSize(width: messagesCollectionView.bounds.width, height: 10) } } diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index 8e022bbd..50f5c4f8 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -31,14 +31,15 @@ struct SampleData { let Cook = Sender(id: "656361", displayName: "Tim Cook") func getMessages() -> [MockMessage] { + let msg1 = MockMessage(text: "Check out this awesome UI library for Chat", sender: Dan, messageId: UUID().uuidString) - let msg2 = MockMessage(text: "This is insane.", sender: Steven, messageId: UUID().uuidString) - let msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Cook, messageId: UUID().uuidString) - let msg4 = MockMessage(text: "My favorite things in life don’t cost any money. It’s really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString) - let msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Cook, messageId: UUID().uuidString) - let msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.", sender: Jobs, messageId: UUID().uuidString) - let msg7 = MockMessage(text: "Remembering that I'll be dead soon is the most important tool I've ever encountered to help me make the big choices in life. Because almost everything - all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important.", sender: Jobs, messageId: UUID().uuidString) - let msg8 = MockMessage(text: "Price is rarely the most important thing. A cheap product might sell some units. Somebody gets it home and they feel great when they pay the money, but then they get it home and use it and the joy is gone.", sender: Cook, messageId: UUID().uuidString) + var msg2 = MockMessage(text: "This is insane.", sender: Steven, messageId: UUID().uuidString) + var msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Cook, messageId: UUID().uuidString) + var msg4 = MockMessage(text: "My favorite things in life don’t cost any money. It’s really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString) + var msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Cook, messageId: UUID().uuidString) + var msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.", sender: Jobs, messageId: UUID().uuidString) + var msg7 = MockMessage(text: "Remembering that I'll be dead soon is the most important tool I've ever encountered to help me make the big choices in life. Because almost everything - all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important.", sender: Jobs, messageId: UUID().uuidString) + var msg8 = MockMessage(text: "Price is rarely the most important thing. A cheap product might sell some units. Somebody gets it home and they feel great when they pay the money, but then they get it home and use it and the joy is gone.", sender: Cook, messageId: UUID().uuidString) let msg9String = "Use .attributedText() to add bold, italic, colored text and more..." let msg9Text = NSString(string: msg9String) @@ -60,12 +61,25 @@ struct SampleData { msg9AttributedText.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: msg9Text.range(of: "colored")) - let msg9 = MockMessage(attributedText: msg9AttributedText, sender: Jobs, messageId: UUID().uuidString) + var msg9 = MockMessage(attributedText: msg9AttributedText, sender: Jobs, messageId: UUID().uuidString) - let msg10 = MockMessage(text: "1-800-555-0000", sender: Steven, messageId: UUID().uuidString) - let msg11 = MockMessage(text: "One Infinite Loop Cupertino, CA 95014 This is some extra text that should not be detected.", sender: Cook, messageId: UUID().uuidString) - let msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Steven, messageId: UUID().uuidString) - let msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString) + var msg10 = MockMessage(text: "1-800-555-0000", sender: Steven, messageId: UUID().uuidString) + var msg11 = MockMessage(text: "One Infinite Loop Cupertino, CA 95014 This is some extra text that should not be detected.", sender: Cook, messageId: UUID().uuidString) + var msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Steven, messageId: UUID().uuidString) + var msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString) + + msg2.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg1.sentDate)! + msg3.sentDate = Calendar.current.date(byAdding: .hour, value: 1, to: msg2.sentDate)! + msg4.sentDate = Calendar.current.date(byAdding: .minute, value: 37, to: msg3.sentDate)! + msg5.sentDate = Calendar.current.date(byAdding: .minute, value: 3, to: msg4.sentDate)! + msg6.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg5.sentDate)! + msg7.sentDate = Calendar.current.date(byAdding: .minute, value: 12, to: msg6.sentDate)! + msg8.sentDate = Calendar.current.date(byAdding: .minute, value: 23, to: msg7.sentDate)! + msg9.sentDate = Calendar.current.date(byAdding: .hour, value: 300, to: msg8.sentDate)! + msg10.sentDate = Calendar.current.date(byAdding: .hour, value: 11, to: msg9.sentDate)! + msg11.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg10.sentDate)! + msg12.sentDate = Calendar.current.date(byAdding: .minute, value: 59, to: msg11.sentDate)! + msg13.sentDate = Calendar.current.date(byAdding: .hour, value: 7, to: msg12.sentDate)! return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13] } @@ -73,6 +87,7 @@ struct SampleData { func getCurrentSender() -> Sender { return Dan } + func getAvatarFor(sender: Sender) -> Avatar { switch sender { diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 45455210..369fef0e 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -25,7 +25,8 @@ 88916B471CF0DFE600469F91 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88916B461CF0DFE600469F91 /* MessageType.swift */; }; B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */; }; B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */; }; - B0147C871F5C129C0035B36E /* TailCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C861F5C129C0035B36E /* TailCorner.swift */; }; + B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */; }; + B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; @@ -83,7 +84,8 @@ 88916B461CF0DFE600469F91 /* MessageType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = ""; }; B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = ""; }; B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; - B0147C861F5C129C0035B36E /* TailCorner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TailCorner.swift; sourceTree = ""; }; + B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = ""; }; + B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; @@ -213,8 +215,8 @@ B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */, B05530B41F493CFA008BB420 /* DetectorType.swift */, B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */, - B0147C861F5C129C0035B36E /* TailCorner.swift */, 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, + B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */, ); name = Models; sourceTree = ""; @@ -232,6 +234,7 @@ B074EE941F35588A00ABB8C8 /* MessageFooterView.swift */, B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */, E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */, + B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */, ); name = Views; sourceTree = ""; @@ -404,6 +407,7 @@ buildActionMask = 2147483647; files = ( 171D5AB91F36712B0053DF69 /* InputTextView.swift in Sources */, + B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */, 38C8679E1F50EA4A00811974 /* InputBarItem.swift in Sources */, B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */, 38C8679A1F50EA1000811974 /* NSConstraintLayoutSet.swift in Sources */, @@ -413,7 +417,6 @@ E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */, B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */, B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */, - B0147C871F5C129C0035B36E /* TailCorner.swift in Sources */, B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */, B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, @@ -423,6 +426,7 @@ B074EE931F35587100ABB8C8 /* MessageHeaderView.swift in Sources */, 376AD1821F4258D80083072A /* TestMessageModel.swift in Sources */, B0655A4D1F244C0600542A83 /* MessageCollectionViewCell.swift in Sources */, + B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */, B0655A2E1F23D8BC00542A83 /* MessagesCollectionView.swift in Sources */, B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */, B074EE951F35588A00ABB8C8 /* MessageFooterView.swift in Sources */, diff --git a/Sources/MessageDateHeader.swift b/Sources/MessageDateHeaderView.swift similarity index 95% rename from Sources/MessageDateHeader.swift rename to Sources/MessageDateHeaderView.swift index 1455e46a..f0632bc8 100644 --- a/Sources/MessageDateHeader.swift +++ b/Sources/MessageDateHeaderView.swift @@ -24,13 +24,13 @@ import UIKit -open class MessageDateHeader: MessageHeaderView { +open class MessageDateHeaderView: MessageHeaderView { // MARK: - Properties open let dateLabel = UILabel() - // MARK: - Initializer + // MARK: - Initializers override init(frame: CGRect) { super.init(frame: frame) diff --git a/Sources/MessageFooterView.swift b/Sources/MessageFooterView.swift index cff8fe1c..57a26a11 100644 --- a/Sources/MessageFooterView.swift +++ b/Sources/MessageFooterView.swift @@ -26,7 +26,9 @@ import UIKit open class MessageFooterView: UICollectionReusableView { - override init(frame: CGRect) { + // MARK: - Initializers + + public override init(frame: CGRect) { super.init(frame: frame) } diff --git a/Sources/MessageHeaderView.swift b/Sources/MessageHeaderView.swift index c2070517..0a37c18e 100644 --- a/Sources/MessageHeaderView.swift +++ b/Sources/MessageHeaderView.swift @@ -26,6 +26,10 @@ import UIKit open class MessageHeaderView: UICollectionReusableView { + // MARK: - Properties + + static let identifier = "MessageHeaderView" + override init(frame: CGRect) { super.init(frame: frame) } diff --git a/Sources/MessagesCollectionView.swift b/Sources/MessagesCollectionView.swift index f584b925..883ca4e7 100644 --- a/Sources/MessagesCollectionView.swift +++ b/Sources/MessagesCollectionView.swift @@ -64,12 +64,12 @@ open class MessagesCollectionView: UICollectionView { scrollToItem(at: indexPath, at: .bottom, animated: animated) } - open func dequeueMessageHeaderView(withReuseIdentifier identifier: String = "MessageHeader", for indexPath: IndexPath) -> MessageHeaderView { + open func dequeueMessageHeaderView(withReuseIdentifier identifier: String = "MessageHeaderView", for indexPath: IndexPath) -> MessageHeaderView { let header = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: identifier, for: indexPath) return header as? MessageHeaderView ?? MessageHeaderView() } - open func dequeueMessageFooterView(withReuseIdentifier identifier: String = "MessageFooter", for indexPath: IndexPath) -> MessageFooterView { + open func dequeueMessageFooterView(withReuseIdentifier identifier: String = "MessageFooterView", for indexPath: IndexPath) -> MessageFooterView { let footer = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: identifier, for: indexPath) return footer as? MessageFooterView ?? MessageFooterView() } diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDataSource.swift index 348b8cee..5b8b2ba0 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDataSource.swift @@ -71,7 +71,7 @@ public extension MessagesDisplayDataSource { } func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { - let header = messagesCollectionView.dequeueMessageHeaderView(withReuseIdentifier: "MessageDateHeader", for: indexPath) as? MessageDateHeader + let header = messagesCollectionView.dequeueMessageHeaderView(withReuseIdentifier: "MessageDateHeaderView", for: indexPath) as? MessageDateHeaderView header?.dateLabel.text = MessageKitDateFormatter.shared.string(from: message.sentDate) return header } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 4578e97a..73522983 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -57,11 +57,7 @@ open class MessagesViewController: UIViewController { setupSubviews() setupConstraints() - - registerReusableCells() - registerReusableHeaders() - registerReusableFooters() - + registerReusableViews() setupDelegates() } @@ -94,20 +90,21 @@ open class MessagesViewController: UIViewController { messagesCollectionView.dataSource = self } - open func registerReusableCells() { - messagesCollectionView.register(MessageCollectionViewCell.self, forCellWithReuseIdentifier: "MessageCell") - } + private func registerReusableViews() { + messagesCollectionView.register(MessageCollectionViewCell.self, + forCellWithReuseIdentifier: "MessageCell") - open func registerReusableFooters() { messagesCollectionView.register(MessageFooterView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, - withReuseIdentifier: "MessageFooter") - } + withReuseIdentifier: "MessageFooterView") - open func registerReusableHeaders() { messagesCollectionView.register(MessageHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, - withReuseIdentifier: "MessageHeader") + withReuseIdentifier: "MessageHeaderView") + + messagesCollectionView.register(MessageDateHeaderView.self, + forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, + withReuseIdentifier: "MessageDateHeaderView") } private func setupSubviews() { From f2277d8cf547d72df949145fb7564d9f2db48006 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 06:08:07 -0500 Subject: [PATCH 21/43] Rename MessagesDisplayDataSource to MessagesDisplayDelegate --- .../Sources/ConversationViewController.swift | 4 +-- Example/Sources/SampleData.swift | 1 - MessageKit.xcodeproj/project.pbxproj | 4 +++ Sources/MessageKit+Availability.swift | 28 +++++++++++++++++++ Sources/MessageStyle.swift | 1 - .../MessagesCollectionViewFlowLayout.swift | 6 ++-- Sources/MessagesDisplayDataSource.swift | 4 +-- Sources/MessagesLayoutDelegate.swift | 2 +- Sources/MessagesViewController.swift | 4 +-- Tests/MessagesDisplayDataSourceTests.swift | 2 +- Tests/TestMessagesViewControllerModel.swift | 2 +- 11 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 Sources/MessageKit+Availability.swift diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 1a8448c5..5a1e9cef 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -131,9 +131,9 @@ extension ConversationViewController: MessagesDataSource { } -// MARK: - MessagesDisplayDataSource +// MARK: - MessagesDisplayDelegate -extension ConversationViewController: MessagesDisplayDataSource { +extension ConversationViewController: MessagesDisplayDelegate { func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar { return SampleData().getAvatarFor(sender: message.sender) diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index 50f5c4f8..ebb8ff83 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -88,7 +88,6 @@ struct SampleData { return Dan } - func getAvatarFor(sender: Sender) -> Avatar { switch sender { case Dan: diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 369fef0e..a9784df6 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ B0147C831F5BE9220035B36E /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */; }; B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */; }; B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; }; + B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C951F600E290035B36E /* MessageKit+Availability.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; @@ -86,6 +87,7 @@ B0147C821F5BE9220035B36E /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = ""; }; B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; }; + B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; @@ -275,6 +277,7 @@ children = ( 88916B3D1CF0DF5100469F91 /* Info.plist */, 88916B3E1CF0DF5100469F91 /* MessageKit.h */, + B0147C951F600E290035B36E /* MessageKit+Availability.swift */, ); name = Supporting; sourceTree = ""; @@ -423,6 +426,7 @@ B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */, 376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */, B0655A2A1F23D77200542A83 /* Sender.swift in Sources */, + B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */, B074EE931F35587100ABB8C8 /* MessageHeaderView.swift in Sources */, 376AD1821F4258D80083072A /* TestMessageModel.swift in Sources */, B0655A4D1F244C0600542A83 /* MessageCollectionViewCell.swift in Sources */, diff --git a/Sources/MessageKit+Availability.swift b/Sources/MessageKit+Availability.swift new file mode 100644 index 00000000..39acfe72 --- /dev/null +++ b/Sources/MessageKit+Availability.swift @@ -0,0 +1,28 @@ +/* + 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 + +@available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use MessagesDisplayDelegate") +public protocol MessagesDisplayDataSource {} diff --git a/Sources/MessageStyle.swift b/Sources/MessageStyle.swift index 82ec17c5..64f6fdc4 100644 --- a/Sources/MessageStyle.swift +++ b/Sources/MessageStyle.swift @@ -70,7 +70,6 @@ public enum MessageStyle { case bubbleTail(TailCorner, TailStyle) case bubbleTailOutline(TailCorner, TailStyle) - // MARK: - Public public var image: UIImage? { diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 29328470..01064a04 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -132,7 +132,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { attributes.cellBottomLabelInsets = cellBottomLabelInsets attributes.direction = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming - let displayDataSource = dataSource as? MessagesDisplayDataSource + let displayDataSource = dataSource as? MessagesDisplayDelegate let avatarPosition = displayDataSource?.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom // Now we set the origins for the frames using the attributes object that contains the calculated sizes @@ -313,7 +313,7 @@ extension MessagesCollectionViewFlowLayout { private func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return 0 } + guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return 0 } guard let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 } let availableWidth = cellTopLabelWidth(for: message) - cellTopLabelInsets.left - cellTopLabelInsets.right @@ -360,7 +360,7 @@ extension MessagesCollectionViewFlowLayout { private func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return 0 } + guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return 0 } guard let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 } let availableWidth = cellBottomLabelWidth(for: message) - cellBottomLabelInsets.left - cellBottomLabelInsets.right diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDataSource.swift index 5b8b2ba0..24880991 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDataSource.swift @@ -24,7 +24,7 @@ import Foundation -public protocol MessagesDisplayDataSource: class, MessagesDataSource { +public protocol MessagesDisplayDelegate: class, MessagesDataSource { func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor @@ -48,7 +48,7 @@ public protocol MessagesDisplayDataSource: class, MessagesDataSource { } -public extension MessagesDisplayDataSource { +public extension MessagesDisplayDelegate { func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? .white : .black diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index ee847129..95505bcb 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -35,7 +35,7 @@ public protocol MessagesLayoutDelegate: class { public extension MessagesLayoutDelegate { func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { - guard let dataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return .zero } + guard let dataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return .zero } let shouldDisplay = dataSource.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) return shouldDisplay ? CGSize(width: messagesCollectionView.bounds.width, height: 12) : .zero } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 73522983..cefdcf03 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -193,7 +193,7 @@ extension MessagesViewController: UICollectionViewDataSource { let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView) - if let displayDataSource = messagesDataSource as? MessagesDisplayDataSource { + if let displayDataSource = messagesDataSource as? MessagesDisplayDelegate { let messageColor = displayDataSource.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) let messageStyle = displayDataSource.messageStyle(for: message, at: indexPath, in: messagesCollectionView) @@ -220,7 +220,7 @@ extension MessagesViewController: UICollectionViewDataSource { public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard let messagesCollectionView = collectionView as? MessagesCollectionView else { return UICollectionReusableView() } - guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDataSource else { return UICollectionReusableView() } + guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return UICollectionReusableView() } let message = displayDataSource.messageForItem(at: indexPath, in: messagesCollectionView) diff --git a/Tests/MessagesDisplayDataSourceTests.swift b/Tests/MessagesDisplayDataSourceTests.swift index 007114de..c711664a 100644 --- a/Tests/MessagesDisplayDataSourceTests.swift +++ b/Tests/MessagesDisplayDataSourceTests.swift @@ -26,7 +26,7 @@ import XCTest @testable import MessageKit -class MessagesDisplayDataSourceTests: XCTestCase { +class MessagesDisplayDelegateTests: XCTestCase { var testClass: TestMessagesViewControllerModel! var window: UIWindow! diff --git a/Tests/TestMessagesViewControllerModel.swift b/Tests/TestMessagesViewControllerModel.swift index db8f0e4e..b87c8067 100644 --- a/Tests/TestMessagesViewControllerModel.swift +++ b/Tests/TestMessagesViewControllerModel.swift @@ -24,7 +24,7 @@ import Foundation -class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDataSource { +class TestMessagesViewControllerModel: MessagesViewController, MessagesDisplayDelegate { var messageList: [TestMessage] = [] From 7873b134b17b8e5e2f3dee224a54894e51597a26 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 06:11:46 -0500 Subject: [PATCH 22/43] Move cellToplabelText, cellBottomLabelText, avatar to MessagesDataSource --- MessageKit.xcodeproj/project.pbxproj | 8 ++++---- Sources/MessagesDataSource.swift | 18 +++++++++++++++++ ...ce.swift => MessagesDisplayDelegate.swift} | 20 +------------------ 3 files changed, 23 insertions(+), 23 deletions(-) rename Sources/{MessagesDisplayDataSource.swift => MessagesDisplayDelegate.swift} (84%) diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index a9784df6..ee97450b 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -46,7 +46,7 @@ B074EE971F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */; }; B074EEA81F3971A600ABB8C8 /* MessageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */; }; B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09643851F286C9E004D0129 /* String+Extensions.swift */; }; - B096438E1F2890FB004D0129 /* MessagesDisplayDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */; }; + B096438E1F2890FB004D0129 /* MessagesDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */; }; B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438F1F289142004D0129 /* UIColor+Extensions.swift */; }; B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */; }; B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */; }; @@ -106,7 +106,7 @@ B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesLayoutDelegate.swift; sourceTree = ""; }; B074EEA71F3971A600ABB8C8 /* MessageLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabel.swift; sourceTree = ""; }; B09643851F286C9E004D0129 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; - B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDataSource.swift; sourceTree = ""; }; + B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDelegate.swift; sourceTree = ""; }; B096438F1F289142004D0129 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = ""; }; B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; @@ -247,7 +247,7 @@ B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */, 88916B461CF0DFE600469F91 /* MessageType.swift */, 882D75831DE507320033F95F /* MessagesDataSource.swift */, - B096438D1F2890FB004D0129 /* MessagesDisplayDataSource.swift */, + B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */, B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */, B074EE961F355FBC00ABB8C8 /* MessagesLayoutDelegate.swift */, B01280F21F4E8798004BCD3E /* MessageLabelDelegate.swift */, @@ -445,7 +445,7 @@ B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */, B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */, 37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */, - B096438E1F2890FB004D0129 /* MessagesDisplayDataSource.swift in Sources */, + B096438E1F2890FB004D0129 /* MessagesDisplayDelegate.swift in Sources */, B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/MessagesDataSource.swift b/Sources/MessagesDataSource.swift index ac275cd8..4014c20c 100644 --- a/Sources/MessagesDataSource.swift +++ b/Sources/MessagesDataSource.swift @@ -34,6 +34,12 @@ public protocol MessagesDataSource: class { func numberOfMessages(in messagesCollectionView: MessagesCollectionView) -> Int + func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar + + func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? + + func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? + } public extension MessagesDataSource { @@ -42,4 +48,16 @@ public extension MessagesDataSource { return message.sender == currentSender() } + func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar { + return Avatar() + } + + func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { + return nil + } + + func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { + return nil + } + } diff --git a/Sources/MessagesDisplayDataSource.swift b/Sources/MessagesDisplayDelegate.swift similarity index 84% rename from Sources/MessagesDisplayDataSource.swift rename to Sources/MessagesDisplayDelegate.swift index 24880991..8fdbc821 100644 --- a/Sources/MessagesDisplayDataSource.swift +++ b/Sources/MessagesDisplayDelegate.swift @@ -32,8 +32,6 @@ public protocol MessagesDisplayDelegate: class, MessagesDataSource { func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor - func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? @@ -41,11 +39,7 @@ public protocol MessagesDisplayDelegate: class, MessagesDataSource { func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? - - func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? - - func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? - + } public extension MessagesDisplayDelegate { @@ -65,10 +59,6 @@ public extension MessagesDisplayDelegate { func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { return .cellBottom } - - func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar { - return Avatar() - } func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { let header = messagesCollectionView.dequeueMessageHeaderView(withReuseIdentifier: "MessageDateHeaderView", for: indexPath) as? MessageDateHeaderView @@ -89,12 +79,4 @@ public extension MessagesDisplayDelegate { return nil } - func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { - return nil - } - - func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { - return nil - } - } From 4a121e328d9985bc94fea863802c99d01f7b995a Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 06:52:00 -0500 Subject: [PATCH 23/43] Make MessageCellDelegate conform to MessageLabelDelegate --- Sources/MessageCellDelegate.swift | 2 +- Sources/MessageCollectionViewCell.swift | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/MessageCellDelegate.swift b/Sources/MessageCellDelegate.swift index e1330eea..262e9e70 100644 --- a/Sources/MessageCellDelegate.swift +++ b/Sources/MessageCellDelegate.swift @@ -24,7 +24,7 @@ import Foundation -public protocol MessageCellDelegate: class { +public protocol MessageCellDelegate: class, MessageLabelDelegate { func didTapMessage(in cell: MessageCollectionViewCell) diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index ba7563a3..cd338e1f 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -46,7 +46,13 @@ open class MessageCollectionViewCell: UICollectionViewCell { return bottomLabel }() - open weak var delegate: MessageCellDelegate? + open weak var delegate: MessageCellDelegate? { + didSet { + cellTopLabel.delegate = delegate + cellBottomLabel.delegate = delegate + messageLabel.delegate = delegate + } + } // MARK: - Initializer @@ -143,4 +149,5 @@ open class MessageCollectionViewCell: UICollectionViewCell { func didTapBottomLabel() { delegate?.didTapBottomLabel(in: self) } + } From d2ecdf6b8c78797d0d3a26c743aced25ddabf587 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 06:52:37 -0500 Subject: [PATCH 24/43] Move avatarPosition to MessagesLayoutDelegate --- Sources/MessagesLayoutDelegate.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index 95505bcb..8855afce 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -26,6 +26,8 @@ import Foundation public protocol MessagesLayoutDelegate: class { + func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition + func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize @@ -34,6 +36,10 @@ public protocol MessagesLayoutDelegate: class { public extension MessagesLayoutDelegate { + func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { + return .cellBottom + } + func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { guard let dataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return .zero } let shouldDisplay = dataSource.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) From f38208c2ccc0236767f36268b569433fe686f737 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 06:52:58 -0500 Subject: [PATCH 25/43] Replace MessagesDisplayDataSource with MessagesDisplayDelegate --- .../Sources/ConversationViewController.swift | 3 +- Sources/MessagesCollectionView.swift | 9 +++- .../MessagesCollectionViewFlowLayout.swift | 12 ++--- Sources/MessagesDisplayDelegate.swift | 16 +++---- Sources/MessagesViewController.swift | 47 +++++++++---------- 5 files changed, 42 insertions(+), 45 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 5a1e9cef..aeca92e0 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -35,9 +35,8 @@ class ConversationViewController: MessagesViewController { messageList = SampleData().getMessages() messagesCollectionView.messagesDataSource = self messagesCollectionView.messagesLayoutDelegate = self - + messagesCollectionView.messagesDisplayDelegate = self messagesCollectionView.messageCellDelegate = self - messagesCollectionView.messageLabelDelegate = self messageInputBar.delegate = self //setupInputBar() } diff --git a/Sources/MessagesCollectionView.swift b/Sources/MessagesCollectionView.swift index 883ca4e7..ad7aeea6 100644 --- a/Sources/MessagesCollectionView.swift +++ b/Sources/MessagesCollectionView.swift @@ -30,11 +30,18 @@ open class MessagesCollectionView: UICollectionView { open weak var messagesDataSource: MessagesDataSource? + open weak var messagesDisplayDelegate: MessagesDisplayDelegate? + open weak var messagesLayoutDelegate: MessagesLayoutDelegate? open weak var messageCellDelegate: MessageCellDelegate? - open weak var messageLabelDelegate: MessageLabelDelegate? + @available(*, deprecated: 0.7.0, message: "messageLabelDelegate is no longer available. MessageCellDelegate now conforms to MessageLabelDelegate") + public weak var messageLabelDelegate: MessageCellDelegate? { + didSet { + messageCellDelegate = messageLabelDelegate + } + } open var showsDateHeaderAfterTimeInterval: TimeInterval = 3600 diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 01064a04..0780e122 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -132,8 +132,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { attributes.cellBottomLabelInsets = cellBottomLabelInsets attributes.direction = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming - let displayDataSource = dataSource as? MessagesDisplayDelegate - let avatarPosition = displayDataSource?.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom + let layoutDelegate = messagesCollectionView.messagesLayoutDelegate + let avatarPosition = layoutDelegate?.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom // Now we set the origins for the frames using the attributes object that contains the calculated sizes attributes.messageContainerFrame.origin = messageContainerOrigin(for: attributes) @@ -313,8 +313,8 @@ extension MessagesCollectionViewFlowLayout { private func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return 0 } - guard let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 } + guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } + guard let topLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 } let availableWidth = cellTopLabelWidth(for: message) - cellTopLabelInsets.left - cellTopLabelInsets.right @@ -360,8 +360,8 @@ extension MessagesCollectionViewFlowLayout { private func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return 0 } - guard let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 } + guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } + guard let bottomLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 } let availableWidth = cellBottomLabelWidth(for: message) - cellBottomLabelInsets.left - cellBottomLabelInsets.right diff --git a/Sources/MessagesDisplayDelegate.swift b/Sources/MessagesDisplayDelegate.swift index 8fdbc821..55072bce 100644 --- a/Sources/MessagesDisplayDelegate.swift +++ b/Sources/MessagesDisplayDelegate.swift @@ -24,7 +24,7 @@ import Foundation -public protocol MessagesDisplayDelegate: class, MessagesDataSource { +public protocol MessagesDisplayDelegate: class { func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor @@ -32,8 +32,6 @@ public protocol MessagesDisplayDelegate: class, MessagesDataSource { func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition - func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool @@ -45,7 +43,7 @@ public protocol MessagesDisplayDelegate: class, MessagesDataSource { public extension MessagesDisplayDelegate { func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { - return isFromCurrentSender(message: message) ? .white : .black + return .darkText } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { @@ -53,11 +51,8 @@ public extension MessagesDisplayDelegate { } func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { - return isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray - } - - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { - return .cellBottom + guard let dataSource = messagesCollectionView.messagesDataSource else { return .white } + return dataSource.isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray } func messageHeaderView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageHeaderView? { @@ -67,10 +62,11 @@ public extension MessagesDisplayDelegate { } func shouldDisplayHeader(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Bool { + guard let dataSource = messagesCollectionView.messagesDataSource else { return false } if indexPath.section == 0 { return false } let previousSection = indexPath.section - 1 let previousIndexPath = IndexPath(item: 0, section: previousSection) - let previousMessage = messageForItem(at: previousIndexPath, in: messagesCollectionView) + let previousMessage = dataSource.messageForItem(at: previousIndexPath, in: messagesCollectionView) let timeIntervalSinceLastMessage = message.sentDate.timeIntervalSince(previousMessage.sentDate) return timeIntervalSinceLastMessage >= messagesCollectionView.showsDateHeaderAfterTimeInterval } diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index cefdcf03..d592133a 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -185,34 +185,28 @@ extension MessagesViewController: UICollectionViewDataSource { if cell.delegate == nil { cell.delegate = cellDelegate } } - if let messageLabelDelegate = messagesCollectionView.messageLabelDelegate { - if cell.messageLabel.delegate == nil { cell.messageLabel.delegate = messageLabelDelegate } - } - guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return cell } let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView) + let avatar = messagesDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView) + let topLabelText = messagesDataSource.cellTopLabelAttributedText(for: message, at: indexPath) + let bottomLabelText = messagesDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) - if let displayDataSource = messagesDataSource as? MessagesDisplayDelegate { - - let messageColor = displayDataSource.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) - let messageStyle = displayDataSource.messageStyle(for: message, at: indexPath, in: messagesCollectionView) - let textColor = displayDataSource.textColor(for: message, at: indexPath, in: messagesCollectionView) - let avatar = displayDataSource.avatar(for: message, at: indexPath, in: messagesCollectionView) - let topLabelText = displayDataSource.cellTopLabelAttributedText(for: message, at: indexPath) - let bottomLabelText = displayDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) - - cell.avatarView.set(avatar: avatar) - cell.messageLabel.textColor = textColor - cell.messageContainerView.messageColor = messageColor - cell.messageContainerView.style = messageStyle - cell.cellTopLabel.attributedText = topLabelText - cell.cellBottomLabel.attributedText = bottomLabelText - - } - + cell.avatarView.set(avatar: avatar) + cell.cellTopLabel.attributedText = topLabelText + cell.cellBottomLabel.attributedText = bottomLabelText cell.configure(with: message) + guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return cell } + + let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) + let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView) + let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView) + + cell.messageLabel.textColor = textColor + cell.messageContainerView.messageColor = messageColor + cell.messageContainerView.style = messageStyle + return cell } @@ -220,15 +214,16 @@ extension MessagesViewController: UICollectionViewDataSource { public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard let messagesCollectionView = collectionView as? MessagesCollectionView else { return UICollectionReusableView() } - guard let displayDataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return UICollectionReusableView() } + guard let dataSource = messagesCollectionView.messagesDataSource else { return UICollectionReusableView() } + guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return UICollectionReusableView() } - let message = displayDataSource.messageForItem(at: indexPath, in: messagesCollectionView) + let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView) switch kind { case UICollectionElementKindSectionHeader: - return displayDataSource.messageHeaderView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageHeaderView() + return displayDelegate.messageHeaderView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageHeaderView() case UICollectionElementKindSectionFooter: - return displayDataSource.messageFooterView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageFooterView() + return displayDelegate.messageFooterView(for: message, at: indexPath, in: messagesCollectionView) ?? MessageFooterView() default: fatalError("Unrecognized element of kind: \(kind)") } From d9def855f9f13bb2aebe38a679510a8393171353 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 07:09:29 -0500 Subject: [PATCH 26/43] Update example, harden deprecation, cleanup label delegates --- .../Sources/ConversationViewController.swift | 42 ++++++++++--------- Sources/MessageCollectionViewCell.swift | 2 - Sources/MessageKit+Availability.swift | 11 ++++- Sources/MessagesCollectionView.swift | 7 ---- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index aeca92e0..2da3b254 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -128,29 +128,10 @@ extension ConversationViewController: MessagesDataSource { return messageList[indexPath.section] } -} - -// MARK: - MessagesDisplayDelegate - -extension ConversationViewController: MessagesDisplayDelegate { - func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar { return SampleData().getAvatarFor(sender: message.sender) } - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { - return .messageBottom - } - - func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { - let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft - return .bubbleTail(corner, .curved) - } - - func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? { - return messagesCollectionView.dequeueMessageFooterView(for: indexPath) - } - func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { let name = message.sender.displayName return NSAttributedString(string: name, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption1)]) @@ -165,10 +146,33 @@ extension ConversationViewController: MessagesDisplayDelegate { } +// MARK: - MessagesDisplayDelegate + +extension ConversationViewController: MessagesDisplayDelegate { + + func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { + return isFromCurrentSender(message: message) ? .white : .darkText + } + + func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { + let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft + return .bubbleTail(corner, .curved) + } + + func messageFooterView(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageFooterView? { + return messagesCollectionView.dequeueMessageFooterView(for: indexPath) + } + +} + // MARK: - MessagesLayoutDelegate extension ConversationViewController: MessagesLayoutDelegate { + func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { + return .messageBottom + } + func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { return CGSize(width: messagesCollectionView.bounds.width, height: 10) diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index cd338e1f..9474a346 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -48,8 +48,6 @@ open class MessageCollectionViewCell: UICollectionViewCell { open weak var delegate: MessageCellDelegate? { didSet { - cellTopLabel.delegate = delegate - cellBottomLabel.delegate = delegate messageLabel.delegate = delegate } } diff --git a/Sources/MessageKit+Availability.swift b/Sources/MessageKit+Availability.swift index 39acfe72..024a6f4c 100644 --- a/Sources/MessageKit+Availability.swift +++ b/Sources/MessageKit+Availability.swift @@ -24,5 +24,14 @@ import Foundation -@available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use MessagesDisplayDelegate") +@available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use MessagesDisplayDelegate.") public protocol MessagesDisplayDataSource {} + +extension MessagesCollectionView { + + @available(*, deprecated: 0.7.0, message: "messageLabelDelegate is no longer available. Please use messageCellDelegate.") + public weak var messageLabelDelegate: MessageLabelDelegate? { + return nil + } + +} diff --git a/Sources/MessagesCollectionView.swift b/Sources/MessagesCollectionView.swift index ad7aeea6..5236b417 100644 --- a/Sources/MessagesCollectionView.swift +++ b/Sources/MessagesCollectionView.swift @@ -36,13 +36,6 @@ open class MessagesCollectionView: UICollectionView { open weak var messageCellDelegate: MessageCellDelegate? - @available(*, deprecated: 0.7.0, message: "messageLabelDelegate is no longer available. MessageCellDelegate now conforms to MessageLabelDelegate") - public weak var messageLabelDelegate: MessageCellDelegate? { - didSet { - messageCellDelegate = messageLabelDelegate - } - } - open var showsDateHeaderAfterTimeInterval: TimeInterval = 3600 private var indexPathForLastItem: IndexPath? { From 8218c9916912991963b7924243b8b82e21e40070 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 07:55:40 -0500 Subject: [PATCH 27/43] Deprecate incoming/outgoing avatar size for avatarSize(for:indexPath:_) --- Sources/MessageKit+Availability.swift | 22 +++++++- .../MessagesCollectionViewFlowLayout.swift | 53 ++++++++----------- Sources/MessagesLayoutDelegate.swift | 6 +++ 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/Sources/MessageKit+Availability.swift b/Sources/MessageKit+Availability.swift index 024a6f4c..38db4c39 100644 --- a/Sources/MessageKit+Availability.swift +++ b/Sources/MessageKit+Availability.swift @@ -24,14 +24,34 @@ import Foundation +// MARK: - MessagesDisplayDataSource + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use MessagesDisplayDelegate.") public protocol MessagesDisplayDataSource {} +// MARK: - MessagesCollectionView + extension MessagesCollectionView { - @available(*, deprecated: 0.7.0, message: "messageLabelDelegate is no longer available. Please use messageCellDelegate.") + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use messageCellDelegate.") public weak var messageLabelDelegate: MessageLabelDelegate? { return nil } } + +// MARK: - MessagesCollectionViewFlowLayout + +extension MessagesCollectionViewFlowLayout { + + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarSize(for:indexPath:messagesCollectionView)") + public var incomingAvatarSize: CGSize { + return .zero + } + + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarSize(for:indexPath:messagesCollectionView)") + public var outgoingAvatarSize: CGSize { + return .zero + } + +} diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 0780e122..44d3c43e 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -38,9 +38,6 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { open var cellBottomLabelInsets: UIEdgeInsets open var bottomLabelExtendsPastAvatar: Bool - open var incomingAvatarSize: CGSize - open var outgoingAvatarSize: CGSize - fileprivate var avatarMessagePadding: CGFloat = 4 fileprivate var messagesCollectionView: MessagesCollectionView? { @@ -70,9 +67,6 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { cellBottomLabelInsets = .zero bottomLabelExtendsPastAvatar = false - incomingAvatarSize = CGSize(width: 30, height: 30) - outgoingAvatarSize = CGSize(width: 30, height: 30) - super.init() sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8) @@ -117,14 +111,13 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { guard let dataSource = messagesCollectionView.messagesDataSource else { return } let indexPath = attributes.indexPath - let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView) // First we set all the sizes so we can pass the attributes object around to calculate the origins attributes.messageContainerFrame = CGRect(origin: .zero, size: messageContainerSize(for: message, at: indexPath)) attributes.cellTopLabelFrame = CGRect(origin: .zero, size: cellTopLabelSize(for: message, at: indexPath)) attributes.cellBottomLabelFrame = CGRect(origin: .zero, size: cellBottomLabelSize(for: message, at: indexPath)) - attributes.avatarFrame = CGRect(origin: .zero, size: avatarSize(for: message)) + attributes.avatarFrame = CGRect(origin: .zero, size: avatarSize(for: message, at: indexPath)) attributes.messageLabelFont = messageLabelFont attributes.messageLabelInsets = messageLabelInsets @@ -164,14 +157,12 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Avatar Calculations - fileprivate func avatarSize(for message: MessageType) -> CGSize { + fileprivate func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { guard let messagesCollectionView = messagesCollectionView else { return .zero } - guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .zero } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - let isOutgoingMessage = messagesDataSource.isFromCurrentSender(message: message) - - return isOutgoingMessage ? outgoingAvatarSize : incomingAvatarSize + return layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView) } fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarPosition: AvatarPosition) -> CGPoint { @@ -216,10 +207,10 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Message Container Calculations - private func availableWidthForMessageContainer(considering message: MessageType) -> CGFloat { + private func availableMessageContainerWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - let avatarWidth = avatarSize(for: message).width - let avatarWidthPlusPadding = avatarWidth == 0 ? 0 : avatarWidth + avatarMessagePadding + let avatarWidth = avatarSize(for: message, at: indexPath).width + let avatarWidthPlusPadding = avatarWidth > 0 ? avatarWidth + avatarMessagePadding : 0 let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right let availableWidth = itemWidth - avatarWidthPlusPadding - horizontalMessageInsets - messageToViewEdgePadding @@ -231,7 +222,7 @@ extension MessagesCollectionViewFlowLayout { private func messageContainerWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { let containerHeight = messageContainerHeight(for: message, at: indexPath) - let availableWidth = availableWidthForMessageContainer(considering: message) + let availableWidth = availableMessageContainerWidth(for: message, at: indexPath) let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right var estimatedWidth: CGFloat = 0 @@ -252,7 +243,7 @@ extension MessagesCollectionViewFlowLayout { private func messageContainerHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - let availableWidth = availableWidthForMessageContainer(considering: message) + let availableWidth = availableMessageContainerWidth(for: message, at: indexPath) let verticalMessageInsets = messageLabelInsets.top + messageLabelInsets.bottom var estimatedHeight: CGFloat = 0 @@ -302,11 +293,12 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Cell Top Label Calculations - private func cellTopLabelWidth(for message: MessageType) -> CGFloat { + private func cellTopLabelWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { if topLabelExtendsPastAvatar { return itemWidth } else { - return itemWidth - avatarSize(for: message).width - avatarMessagePadding - messageToViewEdgePadding + let avatarWidth = avatarSize(for: message, at: indexPath).width + return itemWidth - avatarWidth - avatarMessagePadding - messageToViewEdgePadding } } @@ -316,7 +308,7 @@ extension MessagesCollectionViewFlowLayout { guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } guard let topLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 } - let availableWidth = cellTopLabelWidth(for: message) - cellTopLabelInsets.left - cellTopLabelInsets.right + let availableWidth = cellTopLabelWidth(for: message, at: indexPath) - cellTopLabelInsets.left - cellTopLabelInsets.right let estimatedHeight = topLabelText.height(considering: availableWidth) @@ -326,7 +318,7 @@ extension MessagesCollectionViewFlowLayout { fileprivate func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { - let topLabelWidth = cellTopLabelWidth(for: message) + let topLabelWidth = cellTopLabelWidth(for: message, at: indexPath) let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath) return CGSize(width: topLabelWidth, height: topLabelHeight) @@ -348,12 +340,12 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Cell Bottom Label Calculations - private func cellBottomLabelWidth(for message: MessageType) -> CGFloat { + private func cellBottomLabelWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { if bottomLabelExtendsPastAvatar { return itemWidth } else { - return itemWidth - avatarSize(for: message).width - avatarMessagePadding - messageToViewEdgePadding - + let avatarWidth = avatarSize(for: message, at: indexPath).width + return itemWidth - avatarWidth - avatarMessagePadding - messageToViewEdgePadding } } @@ -362,8 +354,8 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return 0 } guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } guard let bottomLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 } - - let availableWidth = cellBottomLabelWidth(for: message) - cellBottomLabelInsets.left - cellBottomLabelInsets.right + let bottomLabelHorizontalInsets = cellBottomLabelInsets.left + cellBottomLabelInsets.right + let availableWidth = cellBottomLabelWidth(for: message, at: indexPath) - bottomLabelHorizontalInsets let estimatedHeight = bottomLabelText.height(considering: availableWidth) @@ -372,7 +364,7 @@ extension MessagesCollectionViewFlowLayout { fileprivate func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { - let bottomLabelWidth = cellBottomLabelWidth(for: message) + let bottomLabelWidth = cellBottomLabelWidth(for: message, at: indexPath) let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath) return CGSize(width: bottomLabelWidth, height: bottomLabelHeight) @@ -401,15 +393,14 @@ extension MessagesCollectionViewFlowLayout { private func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - let size = avatarSize(for: message) - let avatarHeight = size.height + let avatarHeight = avatarSize(for: message, at: indexPath).height let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath) let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath) let minimumHeight = topLabelHeight + avatarHeight + bottomLabelHeight return minimumHeight - + } private func estimatedCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index 8855afce..8fb0cb52 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -28,6 +28,8 @@ public protocol MessagesLayoutDelegate: class { func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition + func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize + func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize @@ -40,6 +42,10 @@ public extension MessagesLayoutDelegate { return .cellBottom } + func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { + return CGSize(width: 30, height: 30) + } + func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { guard let dataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return .zero } let shouldDisplay = dataSource.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) From 7ac3805ccaa43cd7fba0693284d10264212125d9 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Wed, 6 Sep 2017 10:01:09 -0500 Subject: [PATCH 28/43] Fix messageTextColorDefaultState text --- Sources/MessagesDisplayDelegate.swift | 3 ++- Tests/MessagesDisplayDataSourceTests.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/MessagesDisplayDelegate.swift b/Sources/MessagesDisplayDelegate.swift index 55072bce..2cad787e 100644 --- a/Sources/MessagesDisplayDelegate.swift +++ b/Sources/MessagesDisplayDelegate.swift @@ -43,7 +43,8 @@ public protocol MessagesDisplayDelegate: class { public extension MessagesDisplayDelegate { func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { - return .darkText + guard let dataSource = messagesCollectionView.messagesDataSource else { return .darkText } + return dataSource.isFromCurrentSender(message: message) ? .white : .darkText } func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { diff --git a/Tests/MessagesDisplayDataSourceTests.swift b/Tests/MessagesDisplayDataSourceTests.swift index c711664a..0dddd146 100644 --- a/Tests/MessagesDisplayDataSourceTests.swift +++ b/Tests/MessagesDisplayDataSourceTests.swift @@ -54,7 +54,7 @@ class MessagesDisplayDelegateTests: XCTestCase { func testMessageTextColorDefaultState() { XCTAssertEqual(testClass.textColor(for: testClass.messageList[0], at: IndexPath(item: 0, section: 0), in: testClass.messagesCollectionView), UIColor.white) - XCTAssertEqual(testClass.textColor(for: testClass.messageList[1], at: IndexPath(item: 1, section: 0), in: testClass.messagesCollectionView), UIColor.black) + XCTAssertEqual(testClass.textColor(for: testClass.messageList[1], at: IndexPath(item: 1, section: 0), in: testClass.messagesCollectionView), UIColor.darkText) } func testBackGroundColorDefaultState() { From aa8d867d79c27f8e47192cac14d0236d8bbbcf21 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Thu, 7 Sep 2017 14:40:02 -0500 Subject: [PATCH 29/43] Add CellLabelPosition --- Sources/CellLabelPosition.swift | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Sources/CellLabelPosition.swift diff --git a/Sources/CellLabelPosition.swift b/Sources/CellLabelPosition.swift new file mode 100644 index 00000000..fc8c9f78 --- /dev/null +++ b/Sources/CellLabelPosition.swift @@ -0,0 +1,36 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import Foundation + +public enum CellLabelPosition { + + case cellTrailing + case cellLeading + case cellCenter + case messageTrailing + case messageLeading + case messageCenter + +} From 3081b2e8dae4f51c4b19553165d945873951740c Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Thu, 7 Sep 2017 14:40:43 -0500 Subject: [PATCH 30/43] Add cellTopLabelPosition & cellBtmLabelPosition methods --- Sources/MessagesLayoutDelegate.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index 8fb0cb52..cb52f229 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -28,6 +28,10 @@ public protocol MessagesLayoutDelegate: class { func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition + func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition + + func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition + func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize @@ -46,9 +50,19 @@ public extension MessagesLayoutDelegate { return CGSize(width: 30, height: 30) } + func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter } + return dataSource.isFromCurrentSender(message: message) ? .messageTrailing : .messageLeading + } + + func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter } + return dataSource.isFromCurrentSender(message: message) ? .messageLeading : .messageTrailing + } + func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { - guard let dataSource = messagesCollectionView.messagesDataSource as? MessagesDisplayDelegate else { return .zero } - let shouldDisplay = dataSource.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) + guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return .zero } + let shouldDisplay = displayDelegate.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) return shouldDisplay ? CGSize(width: messagesCollectionView.bounds.width, height: 12) : .zero } From 91b9656bc4e23c9fd3b2230352e34636b4d561ef Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Thu, 7 Sep 2017 14:42:15 -0500 Subject: [PATCH 31/43] Calculate origins from position and trim frame size --- .../MessagesCollectionViewFlowLayout.swift | 346 ++++++++++++------ 1 file changed, 226 insertions(+), 120 deletions(-) diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 44d3c43e..1bc2b969 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -130,8 +130,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { // Now we set the origins for the frames using the attributes object that contains the calculated sizes attributes.messageContainerFrame.origin = messageContainerOrigin(for: attributes) - attributes.cellTopLabelFrame.origin = cellTopLabelOrigin(for: attributes) - attributes.cellBottomLabelFrame.origin = cellBottomLabelOrigin(for: attributes) + attributes.cellTopLabelFrame.origin = cellTopLabelOrigin(for: message, and: attributes) + attributes.cellBottomLabelFrame.origin = cellBottomLabelOrigin(for: message, and: attributes) attributes.avatarFrame.origin = avatarOrigin(for: attributes, with: avatarPosition) } @@ -157,6 +157,7 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Avatar Calculations + /// The size of the avatar view. fileprivate func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { guard let messagesCollectionView = messagesCollectionView else { return .zero } @@ -165,6 +166,7 @@ extension MessagesCollectionViewFlowLayout { return layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView) } + /// The origin for the avatar view. fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarPosition: AvatarPosition) -> CGPoint { var origin = CGPoint.zero @@ -200,75 +202,97 @@ extension MessagesCollectionViewFlowLayout { let avatarMidY = avatarHeight / 2 origin.y = contentHeight - cellTopLabelHeight - messageMidY - avatarMidY } - + return origin + + } + // MARK: - Label Size Calculations + + /// The size required fit a NSAttributedString considering a constrained max width. + fileprivate func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize { + + let estimatedHeight = attributedText.height(considering: maxWidth) + let estimatedWidth = attributedText.width(considering: estimatedHeight) + + let finalHeight = estimatedHeight.rounded(.up) + let finalWidth = estimatedWidth > maxWidth ? maxWidth : estimatedWidth.rounded(.up) + + return CGSize(width: finalWidth, height: finalHeight) + } + + /// The size required to fit a String considering a constrained max width. + fileprivate func labelSize(for text: String, considering maxWidth: CGFloat, and font: UIFont) -> CGSize { + + let estimatedHeight = text.height(considering: maxWidth, and: font) + let estimatedWidth = text.width(considering: estimatedHeight, and: font) + + let finalHeight = estimatedHeight.rounded(.up) + let finalWidth = estimatedWidth > maxWidth ? maxWidth : estimatedWidth.rounded(.up) + + return CGSize(width: finalWidth, height: finalHeight) } // MARK: - Message Container Calculations - private func availableMessageContainerWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { + /// The total value of the horizontal insets for the message label. + private var horizontalMessageInsets: CGFloat { + return messageLabelInsets.left + messageLabelInsets.right + } + + /// The total value of the vertical insets for the message label. + private var verticalMessageInsets: CGFloat { + return messageLabelInsets.top + messageLabelInsets.bottom + } + + /// The total width for all of the cells content excluding the message container. + private func nonMessageContentWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { + + var reservedWidth: CGFloat = 0 let avatarWidth = avatarSize(for: message, at: indexPath).width - let avatarWidthPlusPadding = avatarWidth > 0 ? avatarWidth + avatarMessagePadding : 0 - let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right - let availableWidth = itemWidth - avatarWidthPlusPadding - horizontalMessageInsets - messageToViewEdgePadding + /// Only consider padding if the avatar exists + if avatarWidth > 0 { + reservedWidth += avatarMessagePadding + } + + reservedWidth += avatarWidth + reservedWidth += messageToViewEdgePadding + + return reservedWidth + } + + /// The max width available for the message container. + private func messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { + + let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + horizontalMessageInsets + let availableWidth = itemWidth - reservedWidth return availableWidth - - } - - private func messageContainerWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - - let containerHeight = messageContainerHeight(for: message, at: indexPath) - let availableWidth = availableMessageContainerWidth(for: message, at: indexPath) - let horizontalMessageInsets = messageLabelInsets.left + messageLabelInsets.right - var estimatedWidth: CGFloat = 0 - - switch message.data { - case .text(let text): - estimatedWidth = text.width(considering: containerHeight, and: messageLabelFont).rounded(.up) - case .attributedText(let text): - estimatedWidth = text.width(considering: containerHeight).rounded(.up) - } - - let widthToUse = estimatedWidth > availableWidth ? availableWidth : estimatedWidth - - let finalWidth = widthToUse + horizontalMessageInsets - - return finalWidth - - } - - private func messageContainerHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - - let availableWidth = availableMessageContainerWidth(for: message, at: indexPath) - let verticalMessageInsets = messageLabelInsets.top + messageLabelInsets.bottom - var estimatedHeight: CGFloat = 0 - - switch message.data { - case .text(let text): - estimatedHeight = text.height(considering: availableWidth, and: messageLabelFont) - case .attributedText(let text): - estimatedHeight = text.height(considering: availableWidth) - } - - let finalHeight = estimatedHeight.rounded(.up) + verticalMessageInsets - - return finalHeight - } + /// The size for the message container. fileprivate func messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { - let messageWidth = messageContainerWidth(for: message, at: indexPath) - let messageHeight = messageContainerHeight(for: message, at: indexPath) + let maxWidth = messageContainerMaxWidth(for: message, at: indexPath) - return CGSize(width: messageWidth, height: messageHeight) + var messageContainerSize: CGSize = .zero + switch message.data { + case .text(let text): + messageContainerSize = labelSize(for: text, considering: maxWidth, and: messageLabelFont) + case .attributedText(let text): + messageContainerSize = labelSize(for: text, considering: maxWidth) + } + + messageContainerSize.width += horizontalMessageInsets + messageContainerSize.height += verticalMessageInsets + + return messageContainerSize } + /// The origin for the message container. fileprivate func messageContainerOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { let contentWidth = attributes.frame.width @@ -293,96 +317,164 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Cell Top Label Calculations - private func cellTopLabelWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { + /// The total value of the horizontal insets for the cell top label. + private var topLabelHorizontalInsets: CGFloat { + return cellTopLabelInsets.left + cellTopLabelInsets.right + } + + /// The total value of the vertical insets for the cell top label. + private var topLabelVerticalInsets: CGFloat { + return cellTopLabelInsets.top + cellTopLabelInsets.bottom + } + + /// The max width available for the cell top label. + private func topLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { if topLabelExtendsPastAvatar { - return itemWidth + return itemWidth - topLabelHorizontalInsets } else { - let avatarWidth = avatarSize(for: message, at: indexPath).width - return itemWidth - avatarWidth - avatarMessagePadding - messageToViewEdgePadding + let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + topLabelHorizontalInsets + return itemWidth - reservedWidth } } - private func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - - guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } - guard let topLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return 0 } - - let availableWidth = cellTopLabelWidth(for: message, at: indexPath) - cellTopLabelInsets.left - cellTopLabelInsets.right - - let estimatedHeight = topLabelText.height(considering: availableWidth) - - return estimatedHeight.rounded(.up) - - } - + /// The size of the cell top label. fileprivate func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { - let topLabelWidth = cellTopLabelWidth(for: message, at: indexPath) - let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath) + guard let messagesCollectionView = messagesCollectionView else { return .zero } + guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero } + guard let topLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return .zero } + + let maxWidth = topLabelMaxWidth(for: message, at: indexPath) + var size = labelSize(for: topLabelText, considering: maxWidth) + + size.width += topLabelHorizontalInsets + size.height += topLabelVerticalInsets + + return size - return CGSize(width: topLabelWidth, height: topLabelHeight) - } - fileprivate func cellTopLabelOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { + /// The origin for the cell top label. + fileprivate func cellTopLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { - var origin = CGPoint.zero + guard let messagesCollectionView = messagesCollectionView else { return .zero } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } + let position = layoutDelegate.cellTopLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) - if topLabelExtendsPastAvatar == false { - let avatarWidth = attributes.avatarFrame.width - origin.x = avatarWidth + avatarMessagePadding + var origin: CGPoint = .zero + + switch position { + case .cellTrailing: + origin.x = attributes.frame.width - attributes.cellTopLabelFrame.width + case .cellLeading: + origin.x = 0 + case .cellCenter: + origin.x = itemWidth / 2 - (attributes.cellTopLabelFrame.width / 2) + case .messageTrailing: + switch attributes.direction { + case .incoming: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellTopLabelFrame.width + case .outgoing: + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellTopLabelFrame.width + } + case .messageLeading: + switch attributes.direction { + case .incoming: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + case .outgoing: + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width + } + case .messageCenter: + switch attributes.direction { + case .incoming: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellTopLabelFrame.width / 2) + case .outgoing: + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - (attributes.messageContainerFrame.width / 2) - (attributes.cellTopLabelFrame.width / 2) + } } - + return origin } // MARK: - Cell Bottom Label Calculations - private func cellBottomLabelWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { + /// The total value of the horizontal insets for the cell bottom label + private var bottomLabelHorizontalInsets: CGFloat { + return cellBottomLabelInsets.left + cellBottomLabelInsets.right + } + + /// The total value of the vertical insets for the cell bottom label. + private var bottomLabelVerticalInsets: CGFloat { + return cellBottomLabelInsets.top + cellBottomLabelInsets.bottom + } + + /// The max with available for the cell bottom label. + private func bottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { if bottomLabelExtendsPastAvatar { - return itemWidth + return itemWidth - bottomLabelHorizontalInsets } else { - let avatarWidth = avatarSize(for: message, at: indexPath).width - return itemWidth - avatarWidth - avatarMessagePadding - messageToViewEdgePadding + let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + bottomLabelHorizontalInsets + return itemWidth - reservedWidth } } - private func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - - guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } - guard let bottomLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return 0 } - let bottomLabelHorizontalInsets = cellBottomLabelInsets.left + cellBottomLabelInsets.right - let availableWidth = cellBottomLabelWidth(for: message, at: indexPath) - bottomLabelHorizontalInsets - - let estimatedHeight = bottomLabelText.height(considering: availableWidth) - - return estimatedHeight.rounded(.up) - } - + /// The size for the cell bottom label. fileprivate func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { - let bottomLabelWidth = cellBottomLabelWidth(for: message, at: indexPath) - let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath) + guard let messagesCollectionView = messagesCollectionView else { return .zero } + guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero } + guard let bottomLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return .zero } - return CGSize(width: bottomLabelWidth, height: bottomLabelHeight) + let maxWidth = bottomLabelMaxWidth(for: message, at: indexPath) + var size = labelSize(for: bottomLabelText, considering: maxWidth) + + size.width += bottomLabelHorizontalInsets + size.height += bottomLabelVerticalInsets + + return size } - fileprivate func cellBottomLabelOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { + /// The origin for the cell bottom label. + fileprivate func cellBottomLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { - var origin = CGPoint.zero + guard let messagesCollectionView = messagesCollectionView else { return .zero } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } + let position = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) - let contentHeight = attributes.frame.height - let cellBottomLabelHeight = attributes.cellBottomLabelFrame.height + var origin: CGPoint = .zero - origin.y = contentHeight - cellBottomLabelHeight + origin.y = attributes.frame.height - attributes.cellBottomLabelFrame.height - if bottomLabelExtendsPastAvatar == false { - let avatarWidth = attributes.avatarFrame.width - origin.x = avatarWidth + avatarMessagePadding + switch position { + case .cellTrailing: + origin.x = attributes.frame.width - attributes.cellBottomLabelFrame.width + case .cellLeading: + origin.x = 0 + case .cellCenter: + origin.x = itemWidth / 2 - (attributes.cellBottomLabelFrame.width / 2) + case .messageTrailing: + switch attributes.direction { + case .incoming: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellBottomLabelFrame.width + case .outgoing: + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellBottomLabelFrame.width + } + case .messageLeading: + switch attributes.direction { + case .incoming: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + case .outgoing: + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width + } + case .messageCenter: + switch attributes.direction { + case .incoming: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellBottomLabelFrame.width / 2) + case .outgoing: + origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellBottomLabelFrame.width / 2) + } } return origin @@ -391,28 +483,41 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Cell Size Calculations + /// The minimum height for the cell. private func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { + guard let messagesCollectionView = messagesCollectionView else { return 0 } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } + + let avatarPosition = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + + let topLabelHeight = cellTopLabelSize(for: message, at: indexPath).height let avatarHeight = avatarSize(for: message, at: indexPath).height - let bottomLabelHeight = cellBottomLabelHeight(for: message, at: indexPath) - let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath) + let bottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height - let minimumHeight = topLabelHeight + avatarHeight + bottomLabelHeight - - return minimumHeight + switch avatarPosition { + case .cellTop: + return max(avatarHeight, topLabelHeight) + bottomLabelHeight + case .cellBottom: + return max(avatarHeight, bottomLabelHeight) + topLabelHeight + case .messageTop, .messageCenter, .messageBottom: + return topLabelHeight + avatarHeight + bottomLabelHeight + } } + /// The calculated height of the cell not considering the minimum height. private func estimatedCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - let containerHeight = messageContainerHeight(for: message, at: indexPath) - let topLabelHeight = cellTopLabelHeight(for: message, at: indexPath) - let bottomLabelheight = cellBottomLabelHeight(for: message, at: indexPath) + let messageContainerHeight = messageContainerSize(for: message, at: indexPath).height + let cellTopLabelHeight = cellTopLabelSize(for: message, at: indexPath).height + let cellBottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height - return topLabelHeight + containerHeight + bottomLabelheight + return messageContainerHeight + cellBottomLabelHeight + cellTopLabelHeight } + /// The size for the cell considering all cell contents. open func sizeForItem(at indexPath: IndexPath) -> CGSize { guard let messagesCollectionView = messagesCollectionView else { return .zero } @@ -422,9 +527,10 @@ extension MessagesCollectionViewFlowLayout { let minimumHeight = minimumCellHeight(for: message, at: indexPath) let estimatedHeight = estimatedCellHeight(for: message, at: indexPath) - let finalHeight = estimatedHeight < minimumHeight ? minimumHeight : estimatedHeight - - return CGSize(width: itemWidth, height: finalHeight) + + let itemHeight = estimatedHeight > minimumHeight ? estimatedHeight : minimumHeight + + return CGSize(width: itemWidth, height: itemHeight) } From 42b0841bffae6fe66cefc8a09f1b90d9a4685939 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Thu, 7 Sep 2017 14:42:52 -0500 Subject: [PATCH 32/43] Remove textAlignment setting in cell --- MessageKit.xcodeproj/project.pbxproj | 4 ++++ Sources/MessageCollectionViewCell.swift | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index ee97450b..2e20bd53 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */; }; B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; }; B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C951F600E290035B36E /* MessageKit+Availability.swift */; }; + B0147C981F61AF930035B36E /* CellLabelPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C971F61AF910035B36E /* CellLabelPosition.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; @@ -88,6 +89,7 @@ B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = ""; }; B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; }; B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = ""; }; + B0147C971F61AF910035B36E /* CellLabelPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellLabelPosition.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; @@ -219,6 +221,7 @@ B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */, 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */, + B0147C971F61AF910035B36E /* CellLabelPosition.swift */, ); name = Models; sourceTree = ""; @@ -424,6 +427,7 @@ B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */, + B0147C981F61AF930035B36E /* CellLabelPosition.swift in Sources */, 376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */, B0655A2A1F23D77200542A83 /* Sender.swift in Sources */, B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */, diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index 9474a346..051f7d49 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -94,9 +94,6 @@ open class MessageCollectionViewCell: UICollectionViewCell { cellBottomLabel.frame = attributes.cellBottomLabelFrame cellBottomLabel.textInsets = attributes.cellBottomLabelInsets - cellTopLabel.textAlignment = attributes.direction == .incoming ? .left : .right - cellBottomLabel.textAlignment = attributes.direction == .incoming ? .right : .left - } public func configure(with message: MessageType) { From a940a16196537c950622d9250e35457f7846b2e0 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 01:23:30 -0500 Subject: [PATCH 33/43] Fix cell bottom label origin and max width --- .../Sources/ConversationViewController.swift | 17 ++- Example/Sources/SampleData.swift | 26 +++- Sources/CellLabelPosition.swift | 1 - Sources/MessageCollectionViewCell.swift | 3 + Sources/MessageKit+Availability.swift | 10 ++ .../MessagesCollectionViewFlowLayout.swift | 134 +++++++++++------- 6 files changed, 132 insertions(+), 59 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 2da3b254..27395122 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -133,14 +133,16 @@ extension ConversationViewController: MessagesDataSource { } func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { - let name = message.sender.displayName + var name = message.sender.displayName + name += ";adslfdfkl;dfjskk jdsaf;lkjdsafj FDKJA;FSDH dsfkljhadgjkdsghakj DFGSKJAHGDKLAJHSGD jdhlkdsgjdghjslkjsahdlakjhgdjka DGKJHALGKSJDHAKJ sdgjkahdg djghhd jdhgj jdhg" 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) + var dateString = formatter.string(from: message.sentDate) + //dateString += "dsfjkhdfkjahf dlkjhfdsa kljhflsakjflskajf djh sad jlk hKJDSHLKDSFAHJLF DSKHAFDSL KHFA KLHKJA FHALKFHDL hlkjdsahlfkhasdflkha slk hfdalkhfdlkafds" return NSAttributedString(string: dateString, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption2)]) } @@ -178,6 +180,17 @@ extension ConversationViewController: MessagesLayoutDelegate { return CGSize(width: messagesCollectionView.bounds.width, height: 10) } + func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + switch indexPath.section { + case 0, 6: return .cellLeading + case 1, 7: return .cellTrailing + case 2, 8: return .cellCenter + case 3, 9: return .messageLeading + case 4, 10: return .messageTrailing + default: return .cellCenter + } + } + } // MARK: - MessageCellDelegate diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index ebb8ff83..6623f52f 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -34,11 +34,11 @@ struct SampleData { let msg1 = MockMessage(text: "Check out this awesome UI library for Chat", sender: Dan, messageId: UUID().uuidString) var msg2 = MockMessage(text: "This is insane.", sender: Steven, messageId: UUID().uuidString) - var msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Cook, messageId: UUID().uuidString) + var msg3 = MockMessage(text: "Companies that get confused, that think their goal is revenue or stock price or something. You have to focus on the things that lead to those.", sender: Dan, messageId: UUID().uuidString) var msg4 = MockMessage(text: "My favorite things in life don’t cost any money. It’s really clear that the most precious resource we all have is time.", sender: Jobs, messageId: UUID().uuidString) - var msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Cook, messageId: UUID().uuidString) + var msg5 = MockMessage(text: "You know, this iPhone, as a matter of fact, the engine in here is made in America. And not only are the engines in here made in America, but engines are made in America and are exported. The glass on this phone is made in Kentucky. And so we've been working for years on doing more and more in the United States.", sender: Dan, messageId: UUID().uuidString) var msg6 = MockMessage(text: "I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.", sender: Jobs, messageId: UUID().uuidString) - var msg7 = MockMessage(text: "Remembering that I'll be dead soon is the most important tool I've ever encountered to help me make the big choices in life. Because almost everything - all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important.", sender: Jobs, messageId: UUID().uuidString) + var msg7 = MockMessage(text: "Remembering that I'll be dead soon is the most important tool I've ever encountered to help me make the big choices in life. Because almost everything - all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important.", sender: Dan, messageId: UUID().uuidString) var msg8 = MockMessage(text: "Price is rarely the most important thing. A cheap product might sell some units. Somebody gets it home and they feel great when they pay the money, but then they get it home and use it and the joy is gone.", sender: Cook, messageId: UUID().uuidString) let msg9String = "Use .attributedText() to add bold, italic, colored text and more..." @@ -63,9 +63,9 @@ struct SampleData { var msg9 = MockMessage(attributedText: msg9AttributedText, sender: Jobs, messageId: UUID().uuidString) - var msg10 = MockMessage(text: "1-800-555-0000", sender: Steven, messageId: UUID().uuidString) + var msg10 = MockMessage(text: "1-800-555-0000", sender: Dan, messageId: UUID().uuidString) var msg11 = MockMessage(text: "One Infinite Loop Cupertino, CA 95014 This is some extra text that should not be detected.", sender: Cook, messageId: UUID().uuidString) - var msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Steven, messageId: UUID().uuidString) + var msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Dan, messageId: UUID().uuidString) var msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString) msg2.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg1.sentDate)! @@ -81,9 +81,21 @@ struct SampleData { msg12.sentDate = Calendar.current.date(byAdding: .minute, value: 59, to: msg11.sentDate)! msg13.sentDate = Calendar.current.date(byAdding: .hour, value: 7, to: msg12.sentDate)! - return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13] + let allDan = [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12].map { msg -> MockMessage in + var msg = msg + msg.sender = Dan + return msg + } + + let allSteven = [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12].map { msg -> MockMessage in + var msg = msg + msg.sender = Steven + return msg + } + + return Array(allDan.dropFirst(6)) + Array(allSteven.dropFirst(6)) } - + func getCurrentSender() -> Sender { return Dan } diff --git a/Sources/CellLabelPosition.swift b/Sources/CellLabelPosition.swift index fc8c9f78..a4fdd7f3 100644 --- a/Sources/CellLabelPosition.swift +++ b/Sources/CellLabelPosition.swift @@ -31,6 +31,5 @@ public enum CellLabelPosition { case cellCenter case messageTrailing case messageLeading - case messageCenter } diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index 051f7d49..184801c1 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -35,6 +35,7 @@ open class MessageCollectionViewCell: UICollectionViewCell { open var cellTopLabel: MessageLabel = { let topLabel = MessageLabel() topLabel.enabledDetectors = [] + //topLabel.backgroundColor = .blue return topLabel }() @@ -43,6 +44,7 @@ open class MessageCollectionViewCell: UICollectionViewCell { open var cellBottomLabel: MessageLabel = { let bottomLabel = MessageLabel() bottomLabel.enabledDetectors = [] + bottomLabel.backgroundColor = .red return bottomLabel }() @@ -74,6 +76,7 @@ open class MessageCollectionViewCell: UICollectionViewCell { messageContainerView.addSubview(messageLabel) contentView.addSubview(avatarView) contentView.addSubview(cellBottomLabel) + avatarView.layer.zPosition = 1 } diff --git a/Sources/MessageKit+Availability.swift b/Sources/MessageKit+Availability.swift index 38db4c39..b54a1d29 100644 --- a/Sources/MessageKit+Availability.swift +++ b/Sources/MessageKit+Availability.swift @@ -54,4 +54,14 @@ extension MessagesCollectionViewFlowLayout { return .zero } + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use `cellTopLabelPosition(for:indexPath:messagesCollectionView)") + public var topLabelExtendsPastAvatar: Bool { + return false + } + + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use `cellBottomLabelPosition(for:indexPath:messagesCollectionView)") + public var bottomLabelExtendsPastAvatar: Bool { + return false + } + } diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 1bc2b969..edda88f7 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -33,10 +33,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { open var messageToViewEdgePadding: CGFloat open var cellTopLabelInsets: UIEdgeInsets - open var topLabelExtendsPastAvatar: Bool - open var cellBottomLabelInsets: UIEdgeInsets - open var bottomLabelExtendsPastAvatar: Bool fileprivate var avatarMessagePadding: CGFloat = 4 @@ -62,10 +59,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { messageToViewEdgePadding = 30.0 cellTopLabelInsets = .zero - topLabelExtendsPastAvatar = false - cellBottomLabelInsets = .zero - bottomLabelExtendsPastAvatar = false super.init() @@ -327,6 +321,14 @@ extension MessagesCollectionViewFlowLayout { return cellTopLabelInsets.top + cellTopLabelInsets.bottom } + /// The position for the cell top label. + private func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath) -> CellLabelPosition { + guard let messagesCollectionView = messagesCollectionView else { return .cellCenter } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .cellCenter } + let position = layoutDelegate.cellTopLabelPosition(for: message, at: indexPath, in: messagesCollectionView) + return position + } + /// The max width available for the cell top label. private func topLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { if topLabelExtendsPastAvatar { @@ -357,9 +359,7 @@ extension MessagesCollectionViewFlowLayout { /// The origin for the cell top label. fileprivate func cellTopLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { - guard let messagesCollectionView = messagesCollectionView else { return .zero } - guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - let position = layoutDelegate.cellTopLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + let position = cellTopLabelPosition(for: message, at: attributes.indexPath) var origin: CGPoint = .zero @@ -384,13 +384,6 @@ extension MessagesCollectionViewFlowLayout { case .outgoing: origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width } - case .messageCenter: - switch attributes.direction { - case .incoming: - origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellTopLabelFrame.width / 2) - case .outgoing: - origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - (attributes.messageContainerFrame.width / 2) - (attributes.cellTopLabelFrame.width / 2) - } } return origin @@ -409,14 +402,68 @@ extension MessagesCollectionViewFlowLayout { return cellBottomLabelInsets.top + cellBottomLabelInsets.bottom } + /// The position for the cell bottom label. + private func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath) -> CellLabelPosition { + guard let messagesCollectionView = messagesCollectionView else { return .cellCenter } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .cellCenter } + let position = layoutDelegate.cellBottomLabelPosition(for: message, at: indexPath, in: messagesCollectionView) + return position + } + + public enum AvatarSide { + case cellLeading + case cellTrailing + } + /// The max with available for the cell bottom label. private func bottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - if bottomLabelExtendsPastAvatar { - return itemWidth - bottomLabelHorizontalInsets - } else { - let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + bottomLabelHorizontalInsets - return itemWidth - reservedWidth + + guard let messagesCollectionView = messagesCollectionView else { return 0 } + guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } + + let labelPosition = layoutDelegate.cellBottomLabelPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarPosition = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarSide: AvatarSide = dataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading + let avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width + + switch (labelPosition, avatarSide, avatarPosition) { + + // Avatar is in cell bottom alignment and on the opposing side of label alignment. + // We subtract the avatar width from the available width as to prevent collision. + case (.cellLeading, .cellTrailing, .cellBottom), (.cellTrailing, .cellLeading, .cellBottom): + return itemWidth - avatarWidth - avatarMessagePadding + + // Avatar is on same side that label is aligned to. + // We aren't going to prioritize the layout of one element over another + // -- its a user error -- we return full width. + case (.cellLeading, _, _), (.cellTrailing, _, _): + return itemWidth + + // Avatar is in cell bottom position and label is cell centered. + // We need to provide equal width so we substract the avatar width from both sides. + case (.cellCenter, .cellLeading, .cellBottom), (.cellCenter, .cellTrailing, .cellBottom): + return itemWidth - ((avatarWidth + avatarMessagePadding) * 2) + + // Avatar is not in cell bottom position when the label is centered. + // Here we capture the remaining cases for .cellCenter + case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _): + return itemWidth + + // Available width is equal to the size of the message + case (.messageTrailing, .cellLeading, .cellBottom), (.messageLeading, .cellTrailing, .cellBottom): + return messageContainerSize(for: message, at: indexPath).width + + // Available width begins from message container origin X or max X. + case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _): + return itemWidth - avatarWidth - avatarMessagePadding + + // Available width is the width of the message + the avatar width offset + case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _): + return messageContainerSize(for: message, at: indexPath).width + avatarWidth + avatarMessagePadding + } + } /// The size for the cell bottom label. @@ -441,40 +488,29 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return .zero } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - let position = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + + let labelPosition = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + let avatarSide: AvatarSide = attributes.direction == .incoming ? .cellLeading : .cellTrailing var origin: CGPoint = .zero origin.y = attributes.frame.height - attributes.cellBottomLabelFrame.height - switch position { - case .cellTrailing: - origin.x = attributes.frame.width - attributes.cellBottomLabelFrame.width - case .cellLeading: + switch (labelPosition, avatarSide) { + case (.cellLeading, _): origin.x = 0 - case .cellCenter: - origin.x = itemWidth / 2 - (attributes.cellBottomLabelFrame.width / 2) - case .messageTrailing: - switch attributes.direction { - case .incoming: - origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellBottomLabelFrame.width - case .outgoing: - origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellBottomLabelFrame.width - } - case .messageLeading: - switch attributes.direction { - case .incoming: - origin.x = attributes.avatarFrame.width + avatarMessagePadding - case .outgoing: - origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width - } - case .messageCenter: - switch attributes.direction { - case .incoming: - origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellBottomLabelFrame.width / 2) - case .outgoing: - origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellBottomLabelFrame.width / 2) - } + case (.cellCenter, _): + origin.x = attributes.frame.width / 2 - (attributes.cellBottomLabelFrame.width / 2) + case (.cellTrailing, _): + origin.x = attributes.frame.width - attributes.cellBottomLabelFrame.width + case (.messageLeading, .cellLeading): + origin.x = attributes.avatarFrame.width + avatarMessagePadding + case (.messageLeading, .cellTrailing): + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width + case (.messageTrailing, .cellTrailing): + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellBottomLabelFrame.width + case (.messageTrailing, .cellLeading): + origin.x = attributes.frame.width - messageToViewEdgePadding - attributes.cellBottomLabelFrame.width } return origin From dee15ae0b6a5661f97331c2ac961cce51d42a6c7 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 03:48:33 -0500 Subject: [PATCH 34/43] Fix cell top label origin and max width --- .../Sources/ConversationViewController.swift | 13 +- Sources/MessageCollectionViewCell.swift | 2 +- .../MessagesCollectionViewFlowLayout.swift | 222 +++++++++--------- ...ssagesCollectionViewLayoutAttributes.swift | 5 +- 4 files changed, 124 insertions(+), 118 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 27395122..ee837398 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -134,7 +134,7 @@ extension ConversationViewController: MessagesDataSource { func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { var name = message.sender.displayName - name += ";adslfdfkl;dfjskk jdsaf;lkjdsafj FDKJA;FSDH dsfkljhadgjkdsghakj DFGSKJAHGDKLAJHSGD jdhlkdsgjdghjslkjsahdlakjhgdjka DGKJHALGKSJDHAKJ sdgjkahdg djghhd jdhgj jdhg" + //name += ";ads lf dfkl;dfjs kk jdsaf;lkjdsaf j FDK A;FSDH dsfkl jhadg jkdsg hakj DFGS KJAHG DKLAJH SGD jdhlkdsg jdghjsl jsah dlakjhg djka DGKJHA LGKSJ DHAKJ sdgjka hdg djghhd jdhgj jdhg" return NSAttributedString(string: name, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption1)]) } @@ -191,6 +191,17 @@ extension ConversationViewController: MessagesLayoutDelegate { } } + func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + switch indexPath.section { + case 0, 6: return .cellLeading + case 1, 7: return .cellTrailing + case 2, 8: return .cellCenter + case 3, 9: return .messageLeading + case 4, 10: return .messageTrailing + default: return .cellCenter + } + } + } // MARK: - MessageCellDelegate diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index 184801c1..5a1e222d 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -35,7 +35,7 @@ open class MessageCollectionViewCell: UICollectionViewCell { open var cellTopLabel: MessageLabel = { let topLabel = MessageLabel() topLabel.enabledDetectors = [] - //topLabel.backgroundColor = .blue + topLabel.backgroundColor = .blue return topLabel }() diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index edda88f7..9c2896e1 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -24,6 +24,13 @@ import UIKit +// MARK: - AvatarVerticalAlignment + +enum AvatarHorizontalAlignment { + case cellLeading + case cellTrailing +} + open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { // MARK: - Properties @@ -117,7 +124,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { attributes.messageLabelInsets = messageLabelInsets attributes.cellTopLabelInsets = cellTopLabelInsets attributes.cellBottomLabelInsets = cellBottomLabelInsets - attributes.direction = dataSource.isFromCurrentSender(message: message) ? .outgoing : .incoming + attributes.avatarHorizontalAlignment = dataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading let layoutDelegate = messagesCollectionView.messagesLayoutDelegate let avatarPosition = layoutDelegate?.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom @@ -151,6 +158,15 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Avatar Calculations + /// The horizontal alignment for the message: .cellTrailing or .cellLeading + fileprivate func avatarHorizontalAlignment(for message: MessageType) -> AvatarHorizontalAlignment { + + guard let messagesCollectionView = messagesCollectionView else { return .cellLeading } + guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .cellLeading } + + return messagesDataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading + } + /// The size of the avatar view. fileprivate func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize { @@ -168,10 +184,10 @@ extension MessagesCollectionViewFlowLayout { let contentWidth = attributes.frame.size.width let avatarWidth = attributes.avatarFrame.size.width - switch attributes.direction { - case .incoming: + switch attributes.avatarHorizontalAlignment { + case .cellLeading: origin.x = 0 - case .outgoing: + case .cellTrailing: origin.x = contentWidth - avatarWidth } @@ -239,29 +255,15 @@ extension MessagesCollectionViewFlowLayout { return messageLabelInsets.top + messageLabelInsets.bottom } - /// The total width for all of the cells content excluding the message container. - private func nonMessageContentWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - - var reservedWidth: CGFloat = 0 - - let avatarWidth = avatarSize(for: message, at: indexPath).width - - /// Only consider padding if the avatar exists - if avatarWidth > 0 { - reservedWidth += avatarMessagePadding - } - - reservedWidth += avatarWidth - reservedWidth += messageToViewEdgePadding - - return reservedWidth - } - /// The max width available for the message container. private func messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + horizontalMessageInsets - let availableWidth = itemWidth - reservedWidth + var avatarWidth = avatarSize(for: message, at: indexPath).width + + // Only apply padding if avatar width is greater than zero + if avatarWidth > 0 { avatarWidth += avatarMessagePadding } + + let availableWidth = itemWidth - avatarWidth - messageToViewEdgePadding return availableWidth } @@ -289,20 +291,19 @@ extension MessagesCollectionViewFlowLayout { /// The origin for the message container. fileprivate func messageContainerOrigin(for attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { - let contentWidth = attributes.frame.width - let avatarWidth = attributes.avatarFrame.width - let messageContainerWidth = attributes.messageContainerFrame.width - let cellTopLabelHeight = attributes.cellTopLabelFrame.height + var avatarWidth = attributes.avatarFrame.width + + if avatarWidth > 0 { avatarWidth += avatarMessagePadding } var origin = CGPoint.zero - switch attributes.direction { - case .incoming: - origin.x = avatarWidth + avatarMessagePadding - origin.y = cellTopLabelHeight - case .outgoing: - origin.x = contentWidth - avatarWidth - avatarMessagePadding - messageContainerWidth - origin.y = cellTopLabelHeight + switch attributes.avatarHorizontalAlignment { + case .cellLeading: + origin.x = avatarWidth + origin.y = attributes.cellTopLabelFrame.height + case .cellTrailing: + origin.x = attributes.frame.width - avatarWidth - attributes.messageContainerFrame.width + origin.y = attributes.cellTopLabelFrame.height } return origin @@ -321,21 +322,43 @@ extension MessagesCollectionViewFlowLayout { return cellTopLabelInsets.top + cellTopLabelInsets.bottom } - /// The position for the cell top label. - private func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath) -> CellLabelPosition { - guard let messagesCollectionView = messagesCollectionView else { return .cellCenter } - guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .cellCenter } - let position = layoutDelegate.cellTopLabelPosition(for: message, at: indexPath, in: messagesCollectionView) - return position - } - /// The max width available for the cell top label. private func topLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { - if topLabelExtendsPastAvatar { - return itemWidth - topLabelHorizontalInsets - } else { - let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + topLabelHorizontalInsets - return itemWidth - reservedWidth + + guard let messagesCollectionView = messagesCollectionView else { return 0 } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } + + let labelHorizontal = layoutDelegate.cellTopLabelPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarVertical = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarHorizontal = avatarHorizontalAlignment(for: message) + + var avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width + + // Only apply padding is avatar width is greater than zero + if avatarWidth > 0 { avatarWidth += avatarMessagePadding } + + switch (labelHorizontal, avatarHorizontal, avatarVertical) { + + case (.cellLeading, .cellTrailing, .cellTop), (.cellTrailing, .cellLeading, .cellTop): + return itemWidth - avatarWidth + + case (.cellLeading, _, _), (.cellTrailing, _, _): + return itemWidth + + case (.cellCenter, .cellLeading, .cellTop), (.cellCenter, .cellTrailing, .cellTop): + return itemWidth - (avatarWidth * 2) + + case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _): + return itemWidth + + case (.messageTrailing, .cellLeading, .cellTop), (.messageLeading, .cellTrailing, .cellTop): + return messageContainerSize(for: message, at: indexPath).width + + case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _): + return itemWidth - avatarWidth + + case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _): + return messageContainerSize(for: message, at: indexPath).width + avatarWidth } } @@ -359,33 +382,31 @@ extension MessagesCollectionViewFlowLayout { /// The origin for the cell top label. fileprivate func cellTopLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint { - let position = cellTopLabelPosition(for: message, at: attributes.indexPath) + guard let messagesCollectionView = messagesCollectionView else { return .zero } + guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - var origin: CGPoint = .zero + let labelAlignment = layoutDelegate.cellTopLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + let avatarAlignment = avatarHorizontalAlignment(for: message) - switch position { - case .cellTrailing: - origin.x = attributes.frame.width - attributes.cellTopLabelFrame.width - case .cellLeading: + var origin = CGPoint.zero + + switch (labelAlignment, avatarAlignment) { + case (.cellLeading, _): origin.x = 0 - case .cellCenter: - origin.x = itemWidth / 2 - (attributes.cellTopLabelFrame.width / 2) - case .messageTrailing: - switch attributes.direction { - case .incoming: - origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellTopLabelFrame.width - case .outgoing: - origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellTopLabelFrame.width - } - case .messageLeading: - switch attributes.direction { - case .incoming: - origin.x = attributes.avatarFrame.width + avatarMessagePadding - case .outgoing: - origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width - } + case (.cellCenter, _): + origin.x = attributes.frame.width / 2 - (attributes.cellTopLabelFrame.width / 2) + case (.cellTrailing, _): + origin.x = attributes.frame.width - attributes.cellTopLabelFrame.width + case (.messageLeading, .cellLeading): + origin.x = attributes.avatarFrame.width + avatarMessagePadding + case (.messageLeading, .cellTrailing): + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width + case (.messageTrailing, .cellTrailing): + origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellTopLabelFrame.width + case (.messageTrailing, .cellLeading): + origin.x = attributes.frame.width - messageToViewEdgePadding - attributes.cellTopLabelFrame.width } - + return origin } @@ -402,68 +423,45 @@ extension MessagesCollectionViewFlowLayout { return cellBottomLabelInsets.top + cellBottomLabelInsets.bottom } - /// The position for the cell bottom label. - private func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath) -> CellLabelPosition { - guard let messagesCollectionView = messagesCollectionView else { return .cellCenter } - guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .cellCenter } - let position = layoutDelegate.cellBottomLabelPosition(for: message, at: indexPath, in: messagesCollectionView) - return position - } - - public enum AvatarSide { - case cellLeading - case cellTrailing - } - /// The max with available for the cell bottom label. private func bottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat { guard let messagesCollectionView = messagesCollectionView else { return 0 } - guard let dataSource = messagesCollectionView.messagesDataSource else { return 0 } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } - let labelPosition = layoutDelegate.cellBottomLabelPosition(for: message, at: indexPath, in: messagesCollectionView) - let avatarPosition = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) - let avatarSide: AvatarSide = dataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading - let avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width + let labelHorizontal = layoutDelegate.cellBottomLabelPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarVertical = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarHorizontal = avatarHorizontalAlignment(for: message) - switch (labelPosition, avatarSide, avatarPosition) { + var avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width + + // Only apply padding is avatar width is greater than zero + if avatarWidth > 0 { avatarWidth += avatarMessagePadding } + + switch (labelHorizontal, avatarHorizontal, avatarVertical) { - // Avatar is in cell bottom alignment and on the opposing side of label alignment. - // We subtract the avatar width from the available width as to prevent collision. case (.cellLeading, .cellTrailing, .cellBottom), (.cellTrailing, .cellLeading, .cellBottom): - return itemWidth - avatarWidth - avatarMessagePadding + return itemWidth - avatarWidth - // Avatar is on same side that label is aligned to. - // We aren't going to prioritize the layout of one element over another - // -- its a user error -- we return full width. case (.cellLeading, _, _), (.cellTrailing, _, _): return itemWidth - // Avatar is in cell bottom position and label is cell centered. - // We need to provide equal width so we substract the avatar width from both sides. case (.cellCenter, .cellLeading, .cellBottom), (.cellCenter, .cellTrailing, .cellBottom): - return itemWidth - ((avatarWidth + avatarMessagePadding) * 2) + return itemWidth - (avatarWidth * 2) - // Avatar is not in cell bottom position when the label is centered. - // Here we capture the remaining cases for .cellCenter case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _): return itemWidth - // Available width is equal to the size of the message case (.messageTrailing, .cellLeading, .cellBottom), (.messageLeading, .cellTrailing, .cellBottom): return messageContainerSize(for: message, at: indexPath).width - // Available width begins from message container origin X or max X. case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _): - return itemWidth - avatarWidth - avatarMessagePadding + return itemWidth - avatarWidth - // Available width is the width of the message + the avatar width offset case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _): - return messageContainerSize(for: message, at: indexPath).width + avatarWidth + avatarMessagePadding + return messageContainerSize(for: message, at: indexPath).width + avatarWidth } - } /// The size for the cell bottom label. @@ -489,14 +487,12 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return .zero } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - let labelPosition = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) - let avatarSide: AvatarSide = attributes.direction == .incoming ? .cellLeading : .cellTrailing + let labelAlignment = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + let avatarAlignment = avatarHorizontalAlignment(for: message) - var origin: CGPoint = .zero + var origin = CGPoint(x: 0, y: attributes.frame.height - attributes.cellBottomLabelFrame.height) - origin.y = attributes.frame.height - attributes.cellBottomLabelFrame.height - - switch (labelPosition, avatarSide) { + switch (labelAlignment, avatarAlignment) { case (.cellLeading, _): origin.x = 0 case (.cellCenter, _): diff --git a/Sources/MessagesCollectionViewLayoutAttributes.swift b/Sources/MessagesCollectionViewLayoutAttributes.swift index 89c5dfc8..7bd823ac 100644 --- a/Sources/MessagesCollectionViewLayoutAttributes.swift +++ b/Sources/MessagesCollectionViewLayoutAttributes.swift @@ -28,8 +28,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib // MARK: - Properties - var direction: MessageDirection = .incoming - + var avatarHorizontalAlignment = AvatarHorizontalAlignment.cellLeading var avatarFrame: CGRect = .zero var messageContainerFrame: CGRect = .zero @@ -47,7 +46,7 @@ final class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttrib override func copy(with zone: NSZone? = nil) -> Any { // swiftlint:disable force_cast let copy = super.copy(with: zone) as! MessagesCollectionViewLayoutAttributes - copy.direction = direction + copy.avatarHorizontalAlignment = avatarHorizontalAlignment copy.avatarFrame = avatarFrame copy.messageContainerFrame = messageContainerFrame copy.messageLabelFont = messageLabelFont From 0edc09ec08d45d006ab3c90fe384557bfe6658d1 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 04:08:32 -0500 Subject: [PATCH 35/43] Fix message inset application bug --- .../MessagesCollectionViewFlowLayout.swift | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 9c2896e1..4cfd670b 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -246,12 +246,12 @@ extension MessagesCollectionViewFlowLayout { // MARK: - Message Container Calculations /// The total value of the horizontal insets for the message label. - private var horizontalMessageInsets: CGFloat { + private var messageHorizontalInsets: CGFloat { return messageLabelInsets.left + messageLabelInsets.right } /// The total value of the vertical insets for the message label. - private var verticalMessageInsets: CGFloat { + private var messageVerticalInsets: CGFloat { return messageLabelInsets.top + messageLabelInsets.bottom } @@ -263,7 +263,7 @@ extension MessagesCollectionViewFlowLayout { // Only apply padding if avatar width is greater than zero if avatarWidth > 0 { avatarWidth += avatarMessagePadding } - let availableWidth = itemWidth - avatarWidth - messageToViewEdgePadding + let availableWidth = itemWidth - avatarWidth - messageToViewEdgePadding - messageHorizontalInsets return availableWidth } @@ -282,8 +282,8 @@ extension MessagesCollectionViewFlowLayout { messageContainerSize = labelSize(for: text, considering: maxWidth) } - messageContainerSize.width += horizontalMessageInsets - messageContainerSize.height += verticalMessageInsets + messageContainerSize.width += messageHorizontalInsets + messageContainerSize.height += messageVerticalInsets return messageContainerSize } @@ -340,25 +340,25 @@ extension MessagesCollectionViewFlowLayout { switch (labelHorizontal, avatarHorizontal, avatarVertical) { case (.cellLeading, .cellTrailing, .cellTop), (.cellTrailing, .cellLeading, .cellTop): - return itemWidth - avatarWidth + return itemWidth - avatarWidth - topLabelHorizontalInsets case (.cellLeading, _, _), (.cellTrailing, _, _): - return itemWidth + return itemWidth - topLabelHorizontalInsets case (.cellCenter, .cellLeading, .cellTop), (.cellCenter, .cellTrailing, .cellTop): - return itemWidth - (avatarWidth * 2) + return itemWidth - (avatarWidth * 2) - topLabelHorizontalInsets case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _): - return itemWidth + return itemWidth - topLabelHorizontalInsets case (.messageTrailing, .cellLeading, .cellTop), (.messageLeading, .cellTrailing, .cellTop): - return messageContainerSize(for: message, at: indexPath).width + return messageContainerSize(for: message, at: indexPath).width - topLabelHorizontalInsets case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _): - return itemWidth - avatarWidth + return itemWidth - avatarWidth - topLabelHorizontalInsets case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _): - return messageContainerSize(for: message, at: indexPath).width + avatarWidth + return messageContainerSize(for: message, at: indexPath).width + avatarWidth - topLabelHorizontalInsets } } @@ -441,25 +441,25 @@ extension MessagesCollectionViewFlowLayout { switch (labelHorizontal, avatarHorizontal, avatarVertical) { case (.cellLeading, .cellTrailing, .cellBottom), (.cellTrailing, .cellLeading, .cellBottom): - return itemWidth - avatarWidth + return itemWidth - avatarWidth - bottomLabelHorizontalInsets case (.cellLeading, _, _), (.cellTrailing, _, _): - return itemWidth + return itemWidth - bottomLabelHorizontalInsets case (.cellCenter, .cellLeading, .cellBottom), (.cellCenter, .cellTrailing, .cellBottom): - return itemWidth - (avatarWidth * 2) + return itemWidth - (avatarWidth * 2) - bottomLabelHorizontalInsets case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _): - return itemWidth + return itemWidth - bottomLabelHorizontalInsets case (.messageTrailing, .cellLeading, .cellBottom), (.messageLeading, .cellTrailing, .cellBottom): - return messageContainerSize(for: message, at: indexPath).width + return messageContainerSize(for: message, at: indexPath).width - bottomLabelHorizontalInsets case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _): - return itemWidth - avatarWidth + return itemWidth - avatarWidth - bottomLabelHorizontalInsets case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _): - return messageContainerSize(for: message, at: indexPath).width + avatarWidth + return messageContainerSize(for: message, at: indexPath).width + avatarWidth - bottomLabelHorizontalInsets } } From 59f1ee249fc3862b896e594cb470f96849d73b3e Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 04:51:57 -0500 Subject: [PATCH 36/43] Rename AvatarPosition -> AvatatarAlignment & CellLabelPosition -> LabelAlignment --- .../Sources/ConversationViewController.swift | 6 +-- MessageKit.xcodeproj/project.pbxproj | 16 +++--- ...arPosition.swift => AvatarAlignment.swift} | 2 +- Sources/LabelAlignment.swift | 35 +++++++++++++ ...n.swift => LabelHorizontalAlignment.swift} | 2 +- Sources/MessageKit+Availability.swift | 51 ++++++++++++++++++- .../MessagesCollectionViewFlowLayout.swift | 27 +++++----- Sources/MessagesLayoutDelegate.swift | 12 ++--- 8 files changed, 116 insertions(+), 35 deletions(-) rename Sources/{AvatarPosition.swift => AvatarAlignment.swift} (97%) create mode 100644 Sources/LabelAlignment.swift rename Sources/{CellLabelPosition.swift => LabelHorizontalAlignment.swift} (97%) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index ee837398..83ec4eea 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -171,7 +171,7 @@ extension ConversationViewController: MessagesDisplayDelegate { extension ConversationViewController: MessagesLayoutDelegate { - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { + func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment { return .messageBottom } @@ -180,7 +180,7 @@ extension ConversationViewController: MessagesLayoutDelegate { return CGSize(width: messagesCollectionView.bounds.width, height: 10) } - func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { switch indexPath.section { case 0, 6: return .cellLeading case 1, 7: return .cellTrailing @@ -191,7 +191,7 @@ extension ConversationViewController: MessagesLayoutDelegate { } } - func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { switch indexPath.section { case 0, 6: return .cellLeading case 1, 7: return .cellTrailing diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 2e20bd53..c7546720 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -28,7 +28,7 @@ B0147C901F5ED0810035B36E /* MessageDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */; }; B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; }; B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C951F600E290035B36E /* MessageKit+Availability.swift */; }; - B0147C981F61AF930035B36E /* CellLabelPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C971F61AF910035B36E /* CellLabelPosition.swift */; }; + B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C971F61AF910035B36E /* LabelAlignment.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; @@ -49,7 +49,7 @@ B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09643851F286C9E004D0129 /* String+Extensions.swift */; }; B096438E1F2890FB004D0129 /* MessagesDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */; }; B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438F1F289142004D0129 /* UIColor+Extensions.swift */; }; - B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */; }; + B0AA1F511F42E91A00BAE583 /* AvatarAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */; }; B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */; }; E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */; }; /* End PBXBuildFile section */ @@ -89,7 +89,7 @@ B0147C8F1F5ED0810035B36E /* MessageDateHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDateHeaderView.swift; sourceTree = ""; }; B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; }; B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = ""; }; - B0147C971F61AF910035B36E /* CellLabelPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellLabelPosition.swift; sourceTree = ""; }; + B0147C971F61AF910035B36E /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; @@ -110,7 +110,7 @@ B09643851F286C9E004D0129 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; B096438D1F2890FB004D0129 /* MessagesDisplayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDelegate.swift; sourceTree = ""; }; B096438F1F289142004D0129 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; - B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = ""; }; + B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarAlignment.swift; sourceTree = ""; }; B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContainerView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -216,12 +216,12 @@ B0655A2B1F23D81600542A83 /* MessageData.swift */, 37C936971F38F6AC00853DF2 /* Avatar.swift */, B0655A271F23D71400542A83 /* MessageDirection.swift */, - B0AA1F501F42E91A00BAE583 /* AvatarPosition.swift */, + B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */, B05530B41F493CFA008BB420 /* DetectorType.swift */, B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */, 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */, - B0147C971F61AF910035B36E /* CellLabelPosition.swift */, + B0147C971F61AF910035B36E /* LabelAlignment.swift */, ); name = Models; sourceTree = ""; @@ -427,7 +427,7 @@ B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */, - B0147C981F61AF930035B36E /* CellLabelPosition.swift in Sources */, + B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */, 376AD1881F4259D20083072A /* TestMessagesViewControllerModel.swift in Sources */, B0655A2A1F23D77200542A83 /* Sender.swift in Sources */, B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */, @@ -446,7 +446,7 @@ B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */, B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */, 88916B471CF0DFE600469F91 /* MessageType.swift in Sources */, - B0AA1F511F42E91A00BAE583 /* AvatarPosition.swift in Sources */, + B0AA1F511F42E91A00BAE583 /* AvatarAlignment.swift in Sources */, B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */, 37C936981F38F6AC00853DF2 /* Avatar.swift in Sources */, B096438E1F2890FB004D0129 /* MessagesDisplayDelegate.swift in Sources */, diff --git a/Sources/AvatarPosition.swift b/Sources/AvatarAlignment.swift similarity index 97% rename from Sources/AvatarPosition.swift rename to Sources/AvatarAlignment.swift index d28d7a01..8dba832d 100644 --- a/Sources/AvatarPosition.swift +++ b/Sources/AvatarAlignment.swift @@ -24,7 +24,7 @@ import Foundation -public enum AvatarPosition { +public enum AvatarAlignment { case cellTop case messageTop diff --git a/Sources/LabelAlignment.swift b/Sources/LabelAlignment.swift new file mode 100644 index 00000000..bf749a52 --- /dev/null +++ b/Sources/LabelAlignment.swift @@ -0,0 +1,35 @@ +/* + MIT License + + Copyright (c) 2017 MessageKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import Foundation + +public enum LabelAlignment { + + case cellTrailing + case cellLeading + case cellCenter + case messageTrailing + case messageLeading + +} diff --git a/Sources/CellLabelPosition.swift b/Sources/LabelHorizontalAlignment.swift similarity index 97% rename from Sources/CellLabelPosition.swift rename to Sources/LabelHorizontalAlignment.swift index a4fdd7f3..215417fb 100644 --- a/Sources/CellLabelPosition.swift +++ b/Sources/LabelHorizontalAlignment.swift @@ -24,7 +24,7 @@ import Foundation -public enum CellLabelPosition { +public enum LabelAlignment { case cellTrailing case cellLeading diff --git a/Sources/MessageKit+Availability.swift b/Sources/MessageKit+Availability.swift index b54a1d29..a3d6e73a 100644 --- a/Sources/MessageKit+Availability.swift +++ b/Sources/MessageKit+Availability.swift @@ -31,7 +31,7 @@ public protocol MessagesDisplayDataSource {} // MARK: - MessagesCollectionView -extension MessagesCollectionView { +public extension MessagesCollectionView { @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use messageCellDelegate.") public weak var messageLabelDelegate: MessageLabelDelegate? { @@ -42,7 +42,7 @@ extension MessagesCollectionView { // MARK: - MessagesCollectionViewFlowLayout -extension MessagesCollectionViewFlowLayout { +public extension MessagesCollectionViewFlowLayout { @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarSize(for:indexPath:messagesCollectionView)") public var incomingAvatarSize: CGSize { @@ -65,3 +65,50 @@ extension MessagesCollectionViewFlowLayout { } } + +// MARK: - CellLabelPosition + +@available(*, deprecated: 0.7.0, message: "Renamed to LabelAlignment in MessageKit 0.7.0") +public enum CellLabelPosition { + + case cellTrailing + case cellLeading + case cellCenter + case messageTrailing + case messageLeading + +} + +// MARK: - Avatar Position + +@available(*, deprecated: 0.7.0, message: "Renamed to AvatarAlignment in MessageKit 0.7.0") +public enum AvatarPosition { + + case cellTop + case messageTop + case messageCenter + case messageBottom + case cellBottom + +} + +// MARK: - MessagesLayoutDelegate + +public extension MessagesLayoutDelegate { + + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use avatarAlignment(for:indexPath:messagesCollectionView)") + func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { + return .cellBottom + } + + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use cellTopLabelAlignment(for:indexPath:messagesCollectionView)") + func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + return .cellLeading + } + + @available(*, deprecated: 0.7.0, message: "Removed in MessageKit 0.7.0. Please use cellBottomLabelAlignment(for:indexPath:messagesCollectionView)") + func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + return .cellLeading + } + +} diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 4cfd670b..234bf056 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -127,13 +127,13 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { attributes.avatarHorizontalAlignment = dataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading let layoutDelegate = messagesCollectionView.messagesLayoutDelegate - let avatarPosition = layoutDelegate?.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom + let avatarAlignment = layoutDelegate?.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom // Now we set the origins for the frames using the attributes object that contains the calculated sizes attributes.messageContainerFrame.origin = messageContainerOrigin(for: attributes) attributes.cellTopLabelFrame.origin = cellTopLabelOrigin(for: message, and: attributes) attributes.cellBottomLabelFrame.origin = cellBottomLabelOrigin(for: message, and: attributes) - attributes.avatarFrame.origin = avatarOrigin(for: attributes, with: avatarPosition) + attributes.avatarFrame.origin = avatarOrigin(for: attributes, with: avatarAlignment) } @@ -177,7 +177,7 @@ extension MessagesCollectionViewFlowLayout { } /// The origin for the avatar view. - fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarPosition: AvatarPosition) -> CGPoint { + fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarAlignment: AvatarAlignment) -> CGPoint { var origin = CGPoint.zero @@ -197,12 +197,11 @@ extension MessagesCollectionViewFlowLayout { let cellBottomLabelHeight = attributes.cellBottomLabelFrame.height let messageContainerHeight = attributes.messageContainerFrame.height - switch avatarPosition { + switch avatarAlignment { case .cellTop: - origin.y = topLabelExtendsPastAvatar ? cellTopLabelHeight : 0 + origin.y = 0 case .cellBottom: origin.y = contentHeight - avatarHeight - if bottomLabelExtendsPastAvatar { origin.y -= cellBottomLabelHeight } case .messageTop: origin.y = cellTopLabelHeight case .messageBottom: @@ -328,8 +327,8 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return 0 } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } - let labelHorizontal = layoutDelegate.cellTopLabelPosition(for: message, at: indexPath, in: messagesCollectionView) - let avatarVertical = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + let labelHorizontal = layoutDelegate.cellTopLabelAlignment(for: message, at: indexPath, in: messagesCollectionView) + let avatarVertical = layoutDelegate.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView) let avatarHorizontal = avatarHorizontalAlignment(for: message) var avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width @@ -385,7 +384,7 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return .zero } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - let labelAlignment = layoutDelegate.cellTopLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + let labelAlignment = layoutDelegate.cellTopLabelAlignment(for: message, at: attributes.indexPath, in: messagesCollectionView) let avatarAlignment = avatarHorizontalAlignment(for: message) var origin = CGPoint.zero @@ -429,8 +428,8 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return 0 } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } - let labelHorizontal = layoutDelegate.cellBottomLabelPosition(for: message, at: indexPath, in: messagesCollectionView) - let avatarVertical = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + let labelHorizontal = layoutDelegate.cellBottomLabelAlignment(for: message, at: indexPath, in: messagesCollectionView) + let avatarVertical = layoutDelegate.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView) let avatarHorizontal = avatarHorizontalAlignment(for: message) var avatarWidth = layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView).width @@ -487,7 +486,7 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return .zero } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero } - let labelAlignment = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView) + let labelAlignment = layoutDelegate.cellBottomLabelAlignment(for: message, at: attributes.indexPath, in: messagesCollectionView) let avatarAlignment = avatarHorizontalAlignment(for: message) var origin = CGPoint(x: 0, y: attributes.frame.height - attributes.cellBottomLabelFrame.height) @@ -521,13 +520,13 @@ extension MessagesCollectionViewFlowLayout { guard let messagesCollectionView = messagesCollectionView else { return 0 } guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 } - let avatarPosition = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView) + let avatarAlignment = layoutDelegate.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView) let topLabelHeight = cellTopLabelSize(for: message, at: indexPath).height let avatarHeight = avatarSize(for: message, at: indexPath).height let bottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height - switch avatarPosition { + switch avatarAlignment { case .cellTop: return max(avatarHeight, topLabelHeight) + bottomLabelHeight case .cellBottom: diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index cb52f229..49db48fc 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -26,11 +26,11 @@ import Foundation public protocol MessagesLayoutDelegate: class { - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition + func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment - func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition + func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment - func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition + func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize @@ -42,7 +42,7 @@ public protocol MessagesLayoutDelegate: class { public extension MessagesLayoutDelegate { - func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition { + func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarAlignment { return .cellBottom } @@ -50,12 +50,12 @@ public extension MessagesLayoutDelegate { return CGSize(width: 30, height: 30) } - func cellTopLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter } return dataSource.isFromCurrentSender(message: message) ? .messageTrailing : .messageLeading } - func cellBottomLabelPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellLabelPosition { + func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter } return dataSource.isFromCurrentSender(message: message) ? .messageLeading : .messageTrailing } From c83123061c2cd1ffb0d71896fe383825047ca01b Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 04:56:04 -0500 Subject: [PATCH 37/43] Remove MessageDirection in favfavor of AvatarHorizontalAlignment --- MessageKit.xcodeproj/project.pbxproj | 8 ++++---- ...ion.swift => AvatarHorizontalAlignment.swift} | 16 +++++++++------- Sources/MessagesCollectionViewFlowLayout.swift | 7 ------- 3 files changed, 13 insertions(+), 18 deletions(-) rename Sources/{MessageDirection.swift => AvatarHorizontalAlignment.swift} (88%) diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index c7546720..e137278f 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -29,13 +29,13 @@ B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; }; B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C951F600E290035B36E /* MessageKit+Availability.swift */; }; B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C971F61AF910035B36E /* LabelAlignment.swift */; }; + B0147C9A1F63F1C50035B36E /* AvatarHorizontalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C991F63F1C50035B36E /* AvatarHorizontalAlignment.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; B01E2DDB1F5BC10000E4FA1C /* MessageKitAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */; }; B03FF9AF1F31BB1200754FE5 /* MessageCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */; }; B05530B51F493CFA008BB420 /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05530B41F493CFA008BB420 /* DetectorType.swift */; }; - B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A271F23D71400542A83 /* MessageDirection.swift */; }; B0655A2A1F23D77200542A83 /* Sender.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A291F23D77200542A83 /* Sender.swift */; }; B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A2B1F23D81600542A83 /* MessageData.swift */; }; B0655A2E1F23D8BC00542A83 /* MessagesCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */; }; @@ -90,13 +90,13 @@ B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; }; B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = ""; }; B0147C971F61AF910035B36E /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = ""; }; + B0147C991F63F1C50035B36E /* AvatarHorizontalAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarHorizontalAlignment.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; B01E2DDA1F5BC10000E4FA1C /* MessageKitAssets.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = MessageKitAssets.bundle; sourceTree = ""; }; B03FF9AE1F31BB1200754FE5 /* MessageCellDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellDelegate.swift; sourceTree = ""; }; B05530B41F493CFA008BB420 /* DetectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = ""; }; - B0655A271F23D71400542A83 /* MessageDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDirection.swift; sourceTree = ""; }; B0655A291F23D77200542A83 /* Sender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sender.swift; sourceTree = ""; }; B0655A2B1F23D81600542A83 /* MessageData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageData.swift; sourceTree = ""; }; B0655A2D1F23D8BC00542A83 /* MessagesCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionView.swift; sourceTree = ""; }; @@ -215,13 +215,13 @@ B0655A291F23D77200542A83 /* Sender.swift */, B0655A2B1F23D81600542A83 /* MessageData.swift */, 37C936971F38F6AC00853DF2 /* Avatar.swift */, - B0655A271F23D71400542A83 /* MessageDirection.swift */, B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */, B05530B41F493CFA008BB420 /* DetectorType.swift */, B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */, 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */, B0147C971F61AF910035B36E /* LabelAlignment.swift */, + B0147C991F63F1C50035B36E /* AvatarHorizontalAlignment.swift */, ); name = Models; sourceTree = ""; @@ -423,8 +423,8 @@ E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */, B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */, B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */, + B0147C9A1F63F1C50035B36E /* AvatarHorizontalAlignment.swift in Sources */, B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */, - B0655A281F23D71400542A83 /* MessageDirection.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */, B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */, diff --git a/Sources/MessageDirection.swift b/Sources/AvatarHorizontalAlignment.swift similarity index 88% rename from Sources/MessageDirection.swift rename to Sources/AvatarHorizontalAlignment.swift index cb24a46a..62a07525 100644 --- a/Sources/MessageDirection.swift +++ b/Sources/AvatarHorizontalAlignment.swift @@ -1,18 +1,18 @@ /* MIT License - + Copyright (c) 2017 MessageKit - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,9 +22,11 @@ SOFTWARE. */ -enum MessageDirection { +import Foundation - case incoming - case outgoing +// MARK: - AvatarVerticalAlignment +internal enum AvatarHorizontalAlignment { + case cellLeading + case cellTrailing } diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 234bf056..12d49d19 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -24,13 +24,6 @@ import UIKit -// MARK: - AvatarVerticalAlignment - -enum AvatarHorizontalAlignment { - case cellLeading - case cellTrailing -} - open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { // MARK: - Properties From fc9df34255bfc8f80cddd9dd8d7f34527f420a14 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 08:30:39 -0500 Subject: [PATCH 38/43] Add avatarAlwaysLeading and avatarAlwaysTrailing --- .../Sources/ConversationViewController.swift | 29 +--------- Example/Sources/SampleData.swift | 56 ++++++++----------- Sources/MessageCollectionViewCell.swift | 10 +++- .../MessagesCollectionViewFlowLayout.swift | 26 ++++++++- 4 files changed, 57 insertions(+), 64 deletions(-) diff --git a/Example/Sources/ConversationViewController.swift b/Example/Sources/ConversationViewController.swift index 83ec4eea..37a14381 100644 --- a/Example/Sources/ConversationViewController.swift +++ b/Example/Sources/ConversationViewController.swift @@ -38,6 +38,7 @@ class ConversationViewController: MessagesViewController { messagesCollectionView.messagesDisplayDelegate = self messagesCollectionView.messageCellDelegate = self messageInputBar.delegate = self + //setupInputBar() } @@ -133,16 +134,14 @@ extension ConversationViewController: MessagesDataSource { } func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { - var name = message.sender.displayName - //name += ";ads lf dfkl;dfjs kk jdsaf;lkjdsaf j FDK A;FSDH dsfkl jhadg jkdsg hakj DFGS KJAHG DKLAJH SGD jdhlkdsg jdghjsl jsah dlakjhg djka DGKJHA LGKSJ DHAKJ sdgjka hdg djghhd jdhgj jdhg" + 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 - var dateString = formatter.string(from: message.sentDate) - //dateString += "dsfjkhdfkjahf dlkjhfdsa kljhflsakjflskajf djh sad jlk hKJDSHLKDSFAHJLF DSKHAFDSL KHFA KLHKJA FHALKFHDL hlkjdsahlfkhasdflkha slk hfdalkhfdlkafds" + let dateString = formatter.string(from: message.sentDate) return NSAttributedString(string: dateString, attributes: [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .caption2)]) } @@ -180,28 +179,6 @@ extension ConversationViewController: MessagesLayoutDelegate { return CGSize(width: messagesCollectionView.bounds.width, height: 10) } - func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { - switch indexPath.section { - case 0, 6: return .cellLeading - case 1, 7: return .cellTrailing - case 2, 8: return .cellCenter - case 3, 9: return .messageLeading - case 4, 10: return .messageTrailing - default: return .cellCenter - } - } - - func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { - switch indexPath.section { - case 0, 6: return .cellLeading - case 1, 7: return .cellTrailing - case 2, 8: return .cellCenter - case 3, 9: return .messageLeading - case 4, 10: return .messageTrailing - default: return .cellCenter - } - } - } // MARK: - MessageCellDelegate diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index 6623f52f..e5111f24 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -51,49 +51,39 @@ struct SampleData { } else { msg9AttributedText.addAttributes([NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle], range: msg9Text.range(of: ".attributedText()")) } - - msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)], - range: msg9Text.range(of: "bold")) - - msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)], - range: msg9Text.range(of: "italic")) - - msg9AttributedText.addAttributes([NSForegroundColorAttributeName: UIColor.red], - range: msg9Text.range(of: "colored")) + + msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)], range: msg9Text.range(of: "bold")) + msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)], range: msg9Text.range(of: "italic")) + msg9AttributedText.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: msg9Text.range(of: "colored")) var msg9 = MockMessage(attributedText: msg9AttributedText, sender: Jobs, messageId: UUID().uuidString) - var msg10 = MockMessage(text: "1-800-555-0000", sender: Dan, messageId: UUID().uuidString) var msg11 = MockMessage(text: "One Infinite Loop Cupertino, CA 95014 This is some extra text that should not be detected.", sender: Cook, messageId: UUID().uuidString) - var msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Dan, messageId: UUID().uuidString) - var msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString) + let msg12 = MockMessage(text: "This is an example of the date detector 11/11/2017. April 1st is April Fools Day. Next Friday is not Friday the 13th.", sender: Dan, messageId: UUID().uuidString) + let msg13 = MockMessage(text: "https//:github.com/SD10", sender: Steven, messageId: UUID().uuidString) msg2.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg1.sentDate)! - msg3.sentDate = Calendar.current.date(byAdding: .hour, value: 1, to: msg2.sentDate)! - msg4.sentDate = Calendar.current.date(byAdding: .minute, value: 37, to: msg3.sentDate)! - msg5.sentDate = Calendar.current.date(byAdding: .minute, value: 3, to: msg4.sentDate)! - msg6.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg5.sentDate)! - msg7.sentDate = Calendar.current.date(byAdding: .minute, value: 12, to: msg6.sentDate)! - msg8.sentDate = Calendar.current.date(byAdding: .minute, value: 23, to: msg7.sentDate)! - msg9.sentDate = Calendar.current.date(byAdding: .hour, value: 300, to: msg8.sentDate)! - msg10.sentDate = Calendar.current.date(byAdding: .hour, value: 11, to: msg9.sentDate)! - msg11.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg10.sentDate)! - msg12.sentDate = Calendar.current.date(byAdding: .minute, value: 59, to: msg11.sentDate)! - msg13.sentDate = Calendar.current.date(byAdding: .hour, value: 7, to: msg12.sentDate)! - - let allDan = [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12].map { msg -> MockMessage in + msg3.sentDate = Calendar.current.date(byAdding: .minute, value: 37, to: msg2.sentDate)! + msg4.sentDate = Calendar.current.date(byAdding: .minute, value: 3, to: msg3.sentDate)! + msg5.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg4.sentDate)! + msg6.sentDate = Calendar.current.date(byAdding: .minute, value: 12, to: msg5.sentDate)! + msg7.sentDate = Calendar.current.date(byAdding: .minute, value: 23, to: msg6.sentDate)! + msg8.sentDate = Calendar.current.date(byAdding: .hour, value: 300, to: msg7.sentDate)! + msg9.sentDate = Calendar.current.date(byAdding: .hour, value: 2, to: msg8.sentDate)! + msg10.sentDate = Calendar.current.date(byAdding: .minute, value: 59, to: msg9.sentDate)! + msg11.sentDate = Calendar.current.date(byAdding: .hour, value: 7, to: msg10.sentDate)! + + return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13].map { msg -> MockMessage in var msg = msg - msg.sender = Dan + if msg.sender == Dan { + msg.sender = Steven + } else { + msg.sender = Dan + } return msg } - let allSteven = [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12].map { msg -> MockMessage in - var msg = msg - msg.sender = Steven - return msg - } - - return Array(allDan.dropFirst(6)) + Array(allSteven.dropFirst(6)) + //return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13] } func getCurrentSender() -> Sender { diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index 5a1e222d..c0891bfd 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -35,7 +35,6 @@ open class MessageCollectionViewCell: UICollectionViewCell { open var cellTopLabel: MessageLabel = { let topLabel = MessageLabel() topLabel.enabledDetectors = [] - topLabel.backgroundColor = .blue return topLabel }() @@ -44,7 +43,6 @@ open class MessageCollectionViewCell: UICollectionViewCell { open var cellBottomLabel: MessageLabel = { let bottomLabel = MessageLabel() bottomLabel.enabledDetectors = [] - bottomLabel.backgroundColor = .red return bottomLabel }() @@ -76,7 +74,13 @@ open class MessageCollectionViewCell: UICollectionViewCell { messageContainerView.addSubview(messageLabel) contentView.addSubview(avatarView) contentView.addSubview(cellBottomLabel) - avatarView.layer.zPosition = 1 + + cellTopLabel.backgroundColor = .blue + cellBottomLabel.backgroundColor = .red + contentView.backgroundColor = .yellow + messageContainerView.backgroundColor = .purple + avatarView.backgroundColor = .cyan + messageLabel.backgroundColor = .orange } diff --git a/Sources/MessagesCollectionViewFlowLayout.swift b/Sources/MessagesCollectionViewFlowLayout.swift index 12d49d19..3a23b8f1 100644 --- a/Sources/MessagesCollectionViewFlowLayout.swift +++ b/Sources/MessagesCollectionViewFlowLayout.swift @@ -35,6 +35,22 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { open var cellTopLabelInsets: UIEdgeInsets open var cellBottomLabelInsets: UIEdgeInsets + open var avatarAlwaysLeading: Bool { + willSet { + if newValue { + avatarAlwaysTrailing = false + } + } + } + + open var avatarAlwaysTrailing: Bool { + willSet { + if newValue { + avatarAlwaysLeading = false + } + } + } + fileprivate var avatarMessagePadding: CGFloat = 4 fileprivate var messagesCollectionView: MessagesCollectionView? { @@ -61,6 +77,9 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { cellTopLabelInsets = .zero cellBottomLabelInsets = .zero + avatarAlwaysLeading = false + avatarAlwaysTrailing = false + super.init() sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8) @@ -117,7 +136,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { attributes.messageLabelInsets = messageLabelInsets attributes.cellTopLabelInsets = cellTopLabelInsets attributes.cellBottomLabelInsets = cellBottomLabelInsets - attributes.avatarHorizontalAlignment = dataSource.isFromCurrentSender(message: message) ? .cellTrailing : .cellLeading + attributes.avatarHorizontalAlignment = avatarHorizontalAlignment(for: message) let layoutDelegate = messagesCollectionView.messagesLayoutDelegate let avatarAlignment = layoutDelegate?.avatarAlignment(for: message, at: indexPath, in: messagesCollectionView) ?? .messageBottom @@ -153,6 +172,9 @@ extension MessagesCollectionViewFlowLayout { /// The horizontal alignment for the message: .cellTrailing or .cellLeading fileprivate func avatarHorizontalAlignment(for message: MessageType) -> AvatarHorizontalAlignment { + + if avatarAlwaysTrailing { return .cellTrailing } + if avatarAlwaysLeading { return .cellLeading } guard let messagesCollectionView = messagesCollectionView else { return .cellLeading } guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return .cellLeading } @@ -498,7 +520,7 @@ extension MessagesCollectionViewFlowLayout { case (.messageTrailing, .cellTrailing): origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellBottomLabelFrame.width case (.messageTrailing, .cellLeading): - origin.x = attributes.frame.width - messageToViewEdgePadding - attributes.cellBottomLabelFrame.width + origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellBottomLabelFrame.width } return origin From b7ef4b7c6a7e3ddfd5763d25bdbc0a2749b0043e Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sat, 9 Sep 2017 10:40:28 -0500 Subject: [PATCH 39/43] Fix detector attributes bug --- Sources/MessageCollectionViewCell.swift | 16 +++++++++------- Sources/MessagesViewController.swift | 23 +++++++++++++---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Sources/MessageCollectionViewCell.swift b/Sources/MessageCollectionViewCell.swift index c0891bfd..a596d216 100644 --- a/Sources/MessageCollectionViewCell.swift +++ b/Sources/MessageCollectionViewCell.swift @@ -75,13 +75,6 @@ open class MessageCollectionViewCell: UICollectionViewCell { contentView.addSubview(avatarView) contentView.addSubview(cellBottomLabel) - cellTopLabel.backgroundColor = .blue - cellBottomLabel.backgroundColor = .red - contentView.backgroundColor = .yellow - messageContainerView.backgroundColor = .purple - avatarView.backgroundColor = .cyan - messageLabel.backgroundColor = .orange - } override open func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { @@ -103,6 +96,15 @@ open class MessageCollectionViewCell: UICollectionViewCell { } + override open func prepareForReuse() { + messageLabel.text = nil + messageLabel.attributedText = nil + cellTopLabel.text = nil + cellTopLabel.attributedText = nil + cellBottomLabel.text = nil + cellBottomLabel.attributedText = nil + } + public func configure(with message: MessageType) { switch message.data { diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index d592133a..429c0248 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -192,21 +192,24 @@ extension MessagesViewController: UICollectionViewDataSource { let topLabelText = messagesDataSource.cellTopLabelAttributedText(for: message, at: indexPath) let bottomLabelText = messagesDataSource.cellBottomLabelAttributedText(for: message, at: indexPath) + if let displayDelegate = messagesCollectionView.messagesDisplayDelegate { + + let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) + let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView) + let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView) + + cell.messageLabel.textColor = textColor + cell.messageContainerView.messageColor = messageColor + cell.messageContainerView.style = messageStyle + + } + + // Must be set after configuring displayDelegate properties cell.avatarView.set(avatar: avatar) cell.cellTopLabel.attributedText = topLabelText cell.cellBottomLabel.attributedText = bottomLabelText cell.configure(with: message) - guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return cell } - - let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView) - let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView) - let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView) - - cell.messageLabel.textColor = textColor - cell.messageContainerView.messageColor = messageColor - cell.messageContainerView.style = messageStyle - return cell } From ae458a430566231c5dd8e87f5ca5298747a8334e Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 10 Sep 2017 05:19:41 -0500 Subject: [PATCH 40/43] Fix keyboard contentInset bug - closes #109 --- Sources/MessagesViewController.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/MessagesViewController.swift b/Sources/MessagesViewController.swift index 429c0248..09ddbc42 100644 --- a/Sources/MessagesViewController.swift +++ b/Sources/MessagesViewController.swift @@ -74,7 +74,7 @@ open class MessagesViewController: UIViewController { } override open func viewDidLayoutSubviews() { - messagesCollectionView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: messageInputBar.frame.height, right: 0) + messagesCollectionView.contentInset.top = topLayoutGuide.length } // MARK: - Initializers @@ -260,7 +260,8 @@ extension MessagesViewController: UICollectionViewDataSource { extension MessagesViewController { fileprivate func addKeyboardObservers() { - + + NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardDidShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillHide, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidChangeState), name: .UIKeyboardWillChangeFrame, object: nil) @@ -271,6 +272,7 @@ extension MessagesViewController { NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil) NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil) NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillChangeFrame, object: nil) + NotificationCenter.default.removeObserver(self, name: .UIKeyboardDidShow, object: nil) } func handleKeyboardDidChangeState(_ notification: Notification) { @@ -279,6 +281,10 @@ extension MessagesViewController { let keyboardFrame = self.view.convert(keyboardEndFrame, from: self.view.window) switch notification.name { + case Notification.Name.UIKeyboardDidShow: + // Only runs once + messagesCollectionView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: messageInputBar.frame.height, right: 0) + NotificationCenter.default.removeObserver(self, name: .UIKeyboardDidShow, object: nil) case Notification.Name.UIKeyboardDidChangeFrame, Notification.Name.UIKeyboardWillShow: if (keyboardFrame.origin.y + keyboardFrame.size.height) > view.frame.size.height { // Hardware keyboard is found From 9474b58821528cf9f430fda44992551db6fc5fa7 Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 10 Sep 2017 05:31:11 -0500 Subject: [PATCH 41/43] Update example app for iOS 9 deployment target --- Example/ChatExample.xcodeproj/project.pbxproj | 4 ++-- Example/Sources/SampleData.swift | 17 +++-------------- Sources/MessagesLayoutDelegate.swift | 8 ++++---- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 4ed6401a..6ffd22e3 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -450,7 +450,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -493,7 +493,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_VERSION = 3.0; diff --git a/Example/Sources/SampleData.swift b/Example/Sources/SampleData.swift index e5111f24..3ab63229 100644 --- a/Example/Sources/SampleData.swift +++ b/Example/Sources/SampleData.swift @@ -44,14 +44,9 @@ struct SampleData { let msg9String = "Use .attributedText() to add bold, italic, colored text and more..." let msg9Text = NSString(string: msg9String) let msg9AttributedText = NSMutableAttributedString(string: String(msg9Text)) - msg9AttributedText.addAttribute(NSFontAttributeName, value: UIFont.preferredFont(forTextStyle: .body), range: NSRange(location: 0, length: msg9Text.length)) - - if #available(iOS 9.0, *) { - msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: UIFontWeightBold)], range: msg9Text.range(of: ".attributedText()")) - } else { - msg9AttributedText.addAttributes([NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle], range: msg9Text.range(of: ".attributedText()")) - } + msg9AttributedText.addAttribute(NSFontAttributeName, value: UIFont.preferredFont(forTextStyle: .body), range: NSRange(location: 0, length: msg9Text.length)) + msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.monospacedDigitSystemFont(ofSize: UIFont.systemFontSize, weight: UIFontWeightBold)], range: msg9Text.range(of: ".attributedText()")) msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)], range: msg9Text.range(of: "bold")) msg9AttributedText.addAttributes([NSFontAttributeName: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)], range: msg9Text.range(of: "italic")) msg9AttributedText.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: msg9Text.range(of: "colored")) @@ -75,15 +70,9 @@ struct SampleData { return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13].map { msg -> MockMessage in var msg = msg - if msg.sender == Dan { - msg.sender = Steven - } else { - msg.sender = Dan - } + msg.sender = msg.sender == Dan ? Steven : Dan return msg } - - //return [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13] } func getCurrentSender() -> Sender { diff --git a/Sources/MessagesLayoutDelegate.swift b/Sources/MessagesLayoutDelegate.swift index 49db48fc..b76538b6 100644 --- a/Sources/MessagesLayoutDelegate.swift +++ b/Sources/MessagesLayoutDelegate.swift @@ -46,10 +46,6 @@ public extension MessagesLayoutDelegate { return .cellBottom } - func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { - return CGSize(width: 30, height: 30) - } - func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment { guard let dataSource = messagesCollectionView.messagesDataSource else { return .cellCenter } return dataSource.isFromCurrentSender(message: message) ? .messageTrailing : .messageLeading @@ -60,6 +56,10 @@ public extension MessagesLayoutDelegate { return dataSource.isFromCurrentSender(message: message) ? .messageLeading : .messageTrailing } + func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { + return CGSize(width: 30, height: 30) + } + func headerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize { guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else { return .zero } let shouldDisplay = displayDelegate.shouldDisplayHeader(for: message, at: indexPath, in: messagesCollectionView) From a36082f88c722df93b56d5a4d01c3bbb0d49a42c Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 10 Sep 2017 05:32:50 -0500 Subject: [PATCH 42/43] Update MessageKit.podspec for 0.7.0 and CHANGELOG --- CHANGELOG.md | 6 +++++- MessageKit.podspec | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7d855b..00b91e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,13 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa ## Upcoming release ---------------- +## [Prerelease] 0.7.0 + +This release closes the [0.7 milestone](https://github.com/MessageKit/MessageKit/milestone/8?closed=1) + ## [Prerelease] 0.6.0 -This release closes the [0.6 milestone](https://github.com/MessageKit/MessageKit/milestone/6?closed=1). +This release closes the [0.6 milestone](https://github.com/MessageKit/MessageKit/milestone/7?closed=1). ## [Prerelease] 0.5.0 diff --git a/MessageKit.podspec b/MessageKit.podspec index 6e7c8057..cd9236bf 100644 --- a/MessageKit.podspec +++ b/MessageKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'MessageKit' - s.version = '0.6.0' + s.version = '0.7.0' s.license = { :type => "MIT", :file => "LICENSE.md" } s.summary = 'An elegant messages UI library for iOS.' From 2831baccba0ae610979b15a0754a44f20e67044a Mon Sep 17 00:00:00 2001 From: Steven Deutsch Date: Sun, 10 Sep 2017 06:55:12 -0500 Subject: [PATCH 43/43] Remove duplicate file --- MessageKit.xcodeproj/project.pbxproj | 8 +++--- Sources/LabelHorizontalAlignment.swift | 35 -------------------------- 2 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 Sources/LabelHorizontalAlignment.swift diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index e137278f..d5422460 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -29,7 +29,6 @@ B0147C921F6002150035B36E /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */; }; B0147C961F600E290035B36E /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C951F600E290035B36E /* MessageKit+Availability.swift */; }; B0147C981F61AF930035B36E /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C971F61AF910035B36E /* LabelAlignment.swift */; }; - B0147C9A1F63F1C50035B36E /* AvatarHorizontalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0147C991F63F1C50035B36E /* AvatarHorizontalAlignment.swift */; }; B015E8191F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */; }; B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */; }; B01E2DD81F5BBDB800E4FA1C /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */; }; @@ -51,6 +50,7 @@ B09643901F289142004D0129 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B096438F1F289142004D0129 /* UIColor+Extensions.swift */; }; B0AA1F511F42E91A00BAE583 /* AvatarAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */; }; B0AA1F531F44388C00BAE583 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */; }; + B0E1756F1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */; }; E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */; }; /* End PBXBuildFile section */ @@ -90,7 +90,6 @@ B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = ""; }; B0147C951F600E290035B36E /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = ""; }; B0147C971F61AF910035B36E /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = ""; }; - B0147C991F63F1C50035B36E /* AvatarHorizontalAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarHorizontalAlignment.swift; sourceTree = ""; }; B015E8181F24623D007EDFB6 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = ""; }; B015E81E1F259D8E007EDFB6 /* MessageInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBarDelegate.swift; sourceTree = ""; }; B01E2DD71F5BBDB800E4FA1C /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = ""; }; @@ -112,6 +111,7 @@ B096438F1F289142004D0129 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; B0AA1F501F42E91A00BAE583 /* AvatarAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarAlignment.swift; sourceTree = ""; }; B0AA1F521F44388900BAE583 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; + B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarHorizontalAlignment.swift; sourceTree = ""; }; E8586DB41F59A1C300C9BE9D /* MessageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContainerView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -221,7 +221,7 @@ 38C867981F50EA1000811974 /* NSConstraintLayoutSet.swift */, B0147C911F6002140035B36E /* MessageKitDateFormatter.swift */, B0147C971F61AF910035B36E /* LabelAlignment.swift */, - B0147C991F63F1C50035B36E /* AvatarHorizontalAlignment.swift */, + B0E1756E1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift */, ); name = Models; sourceTree = ""; @@ -423,7 +423,7 @@ E8586DB51F59A1C300C9BE9D /* MessageContainerView.swift in Sources */, B0655A4F1F245C5A00542A83 /* MessagesCollectionViewFlowLayout.swift in Sources */, B0655A2C1F23D81600542A83 /* MessageData.swift in Sources */, - B0147C9A1F63F1C50035B36E /* AvatarHorizontalAlignment.swift in Sources */, + B0E1756F1F655A1600F0DEF6 /* AvatarHorizontalAlignment.swift in Sources */, B09643861F286C9E004D0129 /* String+Extensions.swift in Sources */, B015E81F1F259D8E007EDFB6 /* MessageInputBarDelegate.swift in Sources */, B01280F31F4E8798004BCD3E /* MessageLabelDelegate.swift in Sources */, diff --git a/Sources/LabelHorizontalAlignment.swift b/Sources/LabelHorizontalAlignment.swift deleted file mode 100644 index 215417fb..00000000 --- a/Sources/LabelHorizontalAlignment.swift +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License - - Copyright (c) 2017 MessageKit - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -import Foundation - -public enum LabelAlignment { - - case cellTrailing - case cellLeading - case cellCenter - case messageTrailing - case messageLeading - -}