diff --git a/CHANGELOG.md b/CHANGELOG.md index 4602998d..00a47960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,12 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa - Added support for detection and handling of `NSLink`s inside of messages. [#815](https://github.com/MessageKit/MessageKit/pull/815) by [@jnic](https://github.com/jnic) -- Added customizable `accessoryView`, with a new `MessagesDisplayDelegate` function `configureAccessoryView`, and corresponding size & padding properties in `MessageSizeCalculator`. +- Added customizable `accessoryView`, with a new `MessagesDisplayDelegate` function `configureAccessoryView`, and corresponding size & padding properties in `MessageSizeCalculator`. The `accessoryView` is aligned to the center of the `messageContainerView`. [#710](https://github.com/MessageKit/MessageKit/pull/710) by [@hyouuu](https://github.com/hyouuu) +- Added a tap gesture recognition to the `accessoryView` which calls the `MessageCellDelagate` function `didTapAccessoryView(in:)`. +[#834](https://github.com/MessageKit/MessageKit/pull/834) by [@nathantannar4](https://github.com/nathantannar4) + - Added `additionalBottomInset` property that allows to adjust the bottom content inset automatically set on the messages collection view by the view controller. [#787](https://github.com/MessageKit/MessageKit/pull/787) by [@andreyvit](https://github.com/andreyvit) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index a6ab60f8..6d8fef54 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - MessageInputBar (0.4.0): - - MessageInputBar/Core (= 0.4.0) - - MessageInputBar/Core (0.4.0) + - MessageInputBar (0.4.1): + - MessageInputBar/Core (= 0.4.1) + - MessageInputBar/Core (0.4.1) - MessageKit (1.0.0): - MessageInputBar/Core @@ -18,11 +18,11 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: MessageInputBar: - :commit: 0d2ea1b85a708f37d6b720a3b316e63a39ace4b8 + :commit: faebe27f2dd8f39ea145e75b7296ef48133c099a :git: https://github.com/MessageKit/MessageInputBar.git SPEC CHECKSUMS: - MessageInputBar: d46ecc5b355d13433b8367dcd200e05a6564f0ed + MessageInputBar: e81c7535347f1f7b923de7080409a535a004b6e4 MessageKit: 2bbd13dd6a7c06f42f2d13ed8871d1fe5383b477 PODFILE CHECKSUM: 04c1a805e1997e83bacab1a34787e71e9fe4432b diff --git a/Example/Sources/View Controllers/AdvancedExampleViewController.swift b/Example/Sources/View Controllers/AdvancedExampleViewController.swift index 2ec71ef5..84204cf6 100644 --- a/Example/Sources/View Controllers/AdvancedExampleViewController.swift +++ b/Example/Sources/View Controllers/AdvancedExampleViewController.swift @@ -98,6 +98,11 @@ final class AdvancedExampleViewController: ChatViewController { layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30)) layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap, right: 18)) + layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 200, height: 200)) + layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0)) + layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30)) + layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8)) + messagesCollectionView.messagesLayoutDelegate = self messagesCollectionView.messagesDisplayDelegate = self } @@ -117,6 +122,10 @@ final class AdvancedExampleViewController: ChatViewController { messageInputBar.inputTextView.layer.cornerRadius = 16.0 messageInputBar.inputTextView.layer.masksToBounds = true messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) + configureInputBarItems() + } + + private func configureInputBarItems() { messageInputBar.setRightStackViewWidthConstant(to: 36, animated: false) messageInputBar.sendButton.imageView?.backgroundColor = UIColor(white: 0.85, alpha: 1) messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) @@ -136,14 +145,16 @@ final class AdvancedExampleViewController: ChatViewController { item.title = "\(textView.text.count)/140" let isOverLimit = textView.text.count > 140 item.messageInputBar?.shouldManageSendButtonEnabledState = !isOverLimit // Disable automated management when over limit - item.messageInputBar?.sendButton.isEnabled = !isOverLimit + if isOverLimit { + item.messageInputBar?.sendButton.isEnabled = false + } let color = isOverLimit ? .red : UIColor(white: 0.6, alpha: 1) item.setTitleColor(color, for: .normal) } let bottomItems = [makeButton(named: "ic_at"), makeButton(named: "ic_hashtag"), makeButton(named: "ic_library"), .flexibleSpace, charCountButton] messageInputBar.textViewPadding.bottom = 8 messageInputBar.setStackViewItems(bottomItems, forStack: .bottom, animated: false) - + // This just adds some more flare messageInputBar.sendButton .onEnabled { item in @@ -312,6 +323,19 @@ extension AdvancedExampleViewController: MessagesDisplayDelegate { avatarView.layer.borderColor = UIColor.primaryColor.cgColor } + func configureAccessoryView(_ accessoryView: UIView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) { + // Cells are reused, so only add a button here once. For real use you would need to + // ensure any subviews are removed if not needed + guard accessoryView.subviews.isEmpty else { return } + let button = UIButton(type: .infoLight) + button.tintColor = .primaryColor + accessoryView.addSubview(button) + button.frame = accessoryView.bounds + button.isUserInteractionEnabled = false // respond to accessoryView tap through `MessageCellDelegate` + accessoryView.layer.cornerRadius = accessoryView.frame.height / 2 + accessoryView.backgroundColor = UIColor.primaryColor.withAlphaComponent(0.3) + } + // MARK: - Location Messages func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? { diff --git a/Example/Sources/View Controllers/ChatViewController.swift b/Example/Sources/View Controllers/ChatViewController.swift index 5dbf8104..a8de9166 100644 --- a/Example/Sources/View Controllers/ChatViewController.swift +++ b/Example/Sources/View Controllers/ChatViewController.swift @@ -194,6 +194,10 @@ extension ChatViewController: MessageCellDelegate { print("Bottom label tapped") } + func didTapAccessoryView(in cell: MessageCollectionViewCell) { + print("Accessory view tapped") + } + } // MARK: - MessageLabelDelegate diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index fe5a2e07..8e056eb6 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 1FF377A420087C82004FD648 /* MessageKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377A320087C82004FD648 /* MessageKitError.swift */; }; 1FF377AA20087D78004FD648 /* MessagesViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377A920087D78004FD648 /* MessagesViewController+Menu.swift */; }; 1FF377AC20087DA2004FD648 /* MessagesViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377AB20087DA2004FD648 /* MessagesViewController+Keyboard.swift */; }; + 382C794221705D2000F4FAF5 /* HorizontalEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382C794121705D2000F4FAF5 /* HorizontalEdgeInsets.swift */; }; 38C2AE7C20D4878D00F8079E /* MessageInputBar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38C2AE7B20D4878D00F8079E /* MessageInputBar.framework */; }; 88916B2D1CF0DF2F00469F91 /* MessageKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88916B221CF0DF2F00469F91 /* MessageKit.framework */; }; 8962AC8A1F87AB7D0030B058 /* MessagesCollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC831F87AB230030B058 /* MessagesCollectionViewTests.swift */; }; @@ -128,6 +129,7 @@ 1FF377A320087C82004FD648 /* MessageKitError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageKitError.swift; sourceTree = ""; }; 1FF377A920087D78004FD648 /* MessagesViewController+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesViewController+Menu.swift"; sourceTree = ""; }; 1FF377AB20087DA2004FD648 /* MessagesViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessagesViewController+Keyboard.swift"; sourceTree = ""; }; + 382C794121705D2000F4FAF5 /* HorizontalEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalEdgeInsets.swift; sourceTree = ""; }; 38C2AE7B20D4878D00F8079E /* MessageInputBar.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageInputBar.framework; path = Carthage/Build/iOS/MessageInputBar.framework; sourceTree = ""; }; 88916B221CF0DF2F00469F91 /* MessageKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 88916B2C1CF0DF2F00469F91 /* MessageKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MessageKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -364,11 +366,12 @@ B7A03F1C1F866895006AEF79 /* Avatar.swift */, 1F82D1421FB1B75B00B81A88 /* AvatarPosition.swift */, B7A03F211F866895006AEF79 /* DetectorType.swift */, + 382C794121705D2000F4FAF5 /* HorizontalEdgeInsets.swift */, B7A03F221F866895006AEF79 /* LabelAlignment.swift */, B7A03F1D1F866895006AEF79 /* LocationMessageSnapshotOptions.swift */, B7A03F231F866895006AEF79 /* MessageKind.swift */, - 1FF377A320087C82004FD648 /* MessageKitError.swift */, B7A03F1B1F866895006AEF79 /* MessageKitDateFormatter.swift */, + 1FF377A320087C82004FD648 /* MessageKitError.swift */, B7A03F1F1F866895006AEF79 /* MessageStyle.swift */, B7A03F1A1F866895006AEF79 /* NSConstraintLayoutSet.swift */, B7A03F1E1F866895006AEF79 /* Sender.swift */, @@ -568,6 +571,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 382C794221705D2000F4FAF5 /* HorizontalEdgeInsets.swift in Sources */, B7A03F3C1F866946006AEF79 /* LocationMessageCell.swift in Sources */, 1FF377AA20087D78004FD648 /* MessagesViewController+Menu.swift in Sources */, B7A03F5B1F8669CA006AEF79 /* MessageType.swift in Sources */, diff --git a/Sources/Layout/MessageSizeCalculator.swift b/Sources/Layout/MessageSizeCalculator.swift index 1556ed85..02b12b75 100644 --- a/Sources/Layout/MessageSizeCalculator.swift +++ b/Sources/Layout/MessageSizeCalculator.swift @@ -53,8 +53,8 @@ open class MessageSizeCalculator: CellSizeCalculator { public var incomingAccessoryViewSize = CGSize.zero public var outgoingAccessoryViewSize = CGSize.zero - public var incomingAccessoryViewPadding = UIEdgeInsets.zero - public var outgoingAccessoryViewPadding = UIEdgeInsets.zero + public var incomingAccessoryViewPadding = HorizontalEdgeInsets.zero + public var outgoingAccessoryViewPadding = HorizontalEdgeInsets.zero open override func configure(attributes: UICollectionViewLayoutAttributes) { guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return } @@ -96,39 +96,37 @@ open class MessageSizeCalculator: CellSizeCalculator { let avatarHeight = avatarSize(for: message).height let avatarVerticalPosition = avatarPosition(for: message).vertical let accessoryViewHeight = accessoryViewSize(for: message).height - let accessoryViewVerticalPadding = accessoryViewPadding(for: message).vertical - let accessoryViewTotalHeight = accessoryViewHeight + accessoryViewVerticalPadding switch avatarVerticalPosition { case .messageCenter: let totalLabelHeight: CGFloat = cellTopLabelHeight + messageTopLabelHeight + messageContainerHeight + messageVerticalPadding + messageBottomLabelHeight let cellHeight = max(avatarHeight, totalLabelHeight) - return max(accessoryViewTotalHeight, cellHeight) + return max(cellHeight, accessoryViewHeight) case .messageBottom: var cellHeight: CGFloat = 0 cellHeight += messageBottomLabelHeight let labelsHeight = messageContainerHeight + messageVerticalPadding + cellTopLabelHeight + messageTopLabelHeight cellHeight += max(labelsHeight, avatarHeight) - return max(accessoryViewTotalHeight, cellHeight) + return max(cellHeight, accessoryViewHeight) case .messageTop: var cellHeight: CGFloat = 0 cellHeight += cellTopLabelHeight cellHeight += messageTopLabelHeight let labelsHeight = messageContainerHeight + messageVerticalPadding + messageBottomLabelHeight cellHeight += max(labelsHeight, avatarHeight) - return max(accessoryViewTotalHeight, cellHeight) + return max(cellHeight, accessoryViewHeight) case .messageLabelTop: var cellHeight: CGFloat = 0 cellHeight += cellTopLabelHeight let messageLabelsHeight = messageContainerHeight + messageBottomLabelHeight + messageVerticalPadding + messageTopLabelHeight cellHeight += max(messageLabelsHeight, avatarHeight) - return max(accessoryViewTotalHeight, cellHeight) + return max(cellHeight, accessoryViewHeight) case .cellTop, .cellBottom: let totalLabelHeight: CGFloat = cellTopLabelHeight + messageTopLabelHeight + messageContainerHeight + messageVerticalPadding + messageBottomLabelHeight let cellHeight = max(avatarHeight, totalLabelHeight) - return max(accessoryViewTotalHeight, cellHeight) + return max(cellHeight, accessoryViewHeight) } } @@ -207,7 +205,7 @@ open class MessageSizeCalculator: CellSizeCalculator { return isFromCurrentSender ? outgoingAccessoryViewSize : incomingAccessoryViewSize } - public func accessoryViewPadding(for message: MessageType) -> UIEdgeInsets { + public func accessoryViewPadding(for message: MessageType) -> HorizontalEdgeInsets { let dataSource = messagesLayout.messagesDataSource let isFromCurrentSender = dataSource.isFromCurrentSender(message: message) return isFromCurrentSender ? outgoingAccessoryViewPadding : incomingAccessoryViewPadding diff --git a/Sources/Layout/MessagesCollectionViewFlowLayout.swift b/Sources/Layout/MessagesCollectionViewFlowLayout.swift index 4e3d71b7..26a734c5 100644 --- a/Sources/Layout/MessagesCollectionViewFlowLayout.swift +++ b/Sources/Layout/MessagesCollectionViewFlowLayout.swift @@ -231,12 +231,12 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { } /// Set `incomingAccessoryViewSize` of all `MessageSizeCalculator`s - public func setMessageIncomingAccessoryViewPadding(_ newPadding: UIEdgeInsets) { + public func setMessageIncomingAccessoryViewPadding(_ newPadding: HorizontalEdgeInsets) { messageSizeCalculators().forEach { $0.incomingAccessoryViewPadding = newPadding } } /// Set `outgoingAvatarSize` of all `MessageSizeCalculator`s - public func setMessageOutgoingAccessoryViewPadding(_ newPadding: UIEdgeInsets) { + public func setMessageOutgoingAccessoryViewPadding(_ newPadding: HorizontalEdgeInsets) { messageSizeCalculators().forEach { $0.outgoingAccessoryViewPadding = newPadding } } diff --git a/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift b/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift index 9dcde3cf..e055ac57 100644 --- a/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift +++ b/Sources/Layout/MessagesCollectionViewLayoutAttributes.swift @@ -47,7 +47,7 @@ open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttribu public var messageBottomLabelSize: CGSize = .zero public var accessoryViewSize: CGSize = .zero - public var accessoryViewPadding: UIEdgeInsets = .zero + public var accessoryViewPadding: HorizontalEdgeInsets = .zero // MARK: - Methods diff --git a/Sources/Models/HorizontalEdgeInsets.swift b/Sources/Models/HorizontalEdgeInsets.swift new file mode 100644 index 00000000..9ec2f0f5 --- /dev/null +++ b/Sources/Models/HorizontalEdgeInsets.swift @@ -0,0 +1,55 @@ +/* + MIT License + + Copyright (c) 2017-2018 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 + +/// A varient of `UIEdgeInsets` that only has horizontal inset properties +public struct HorizontalEdgeInsets { + + public var left: CGFloat + public var right: CGFloat + + public init(left: CGFloat, right: CGFloat) { + self.left = left + self.right = right + } + + public static var zero: HorizontalEdgeInsets { + return HorizontalEdgeInsets(left: 0, right: 0) + } +} + +extension HorizontalEdgeInsets: Equatable { + + public static func == (lhs: HorizontalEdgeInsets, rhs: HorizontalEdgeInsets) -> Bool { + return lhs.left == rhs.left && lhs.right == rhs.right + } +} + +extension HorizontalEdgeInsets { + + internal var horizontal: CGFloat { + return left + right + } +} diff --git a/Sources/Protocols/MessageCellDelegate.swift b/Sources/Protocols/MessageCellDelegate.swift index 1300a223..4fbe9da9 100644 --- a/Sources/Protocols/MessageCellDelegate.swift +++ b/Sources/Protocols/MessageCellDelegate.swift @@ -77,6 +77,16 @@ public protocol MessageCellDelegate: MessageLabelDelegate { /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource` /// method `messageForItem(at:indexPath:messagesCollectionView)`. func didTapMessageBottomLabel(in cell: MessageCollectionViewCell) + + /// Triggered when a tap occurs in the accessoryView. + /// + /// - Parameters: + /// - cell: The cell where the tap occurred. + /// + /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s + /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource` + /// method `messageForItem(at:indexPath:messagesCollectionView)`. + func didTapAccessoryView(in cell: MessageCollectionViewCell) } @@ -91,4 +101,6 @@ public extension MessageCellDelegate { func didTapMessageTopLabel(in cell: MessageCollectionViewCell) {} func didTapMessageBottomLabel(in cell: MessageCollectionViewCell) {} + + func didTapAccessoryView(in cell: MessageCollectionViewCell) {} } diff --git a/Sources/Views/Cells/MessageContentCell.swift b/Sources/Views/Cells/MessageContentCell.swift index be7a5a9e..b60f9121 100644 --- a/Sources/Views/Cells/MessageContentCell.swift +++ b/Sources/Views/Cells/MessageContentCell.swift @@ -156,6 +156,8 @@ open class MessageContentCell: MessageCollectionViewCell { delegate?.didTapMessageTopLabel(in: self) case messageBottomLabel.frame.contains(touchLocation): delegate?.didTapMessageBottomLabel(in: self) + case accessoryView.frame.contains(touchLocation): + delegate?.didTapAccessoryView(in: self) default: break } @@ -223,7 +225,12 @@ open class MessageContentCell: MessageCollectionViewCell { fallthrough } default: - origin.y = attributes.cellTopLabelSize.height + attributes.messageTopLabelSize.height + attributes.messageContainerPadding.top + if attributes.accessoryViewSize.height > attributes.messageContainerSize.height { + let messageHeight = attributes.messageContainerSize.height + attributes.messageContainerPadding.vertical + origin.y = (attributes.size.height / 2) - (messageHeight / 2) + } else { + origin.y = attributes.cellTopLabelSize.height + attributes.messageTopLabelSize.height + attributes.messageContainerPadding.top + } } switch attributes.avatarPosition.horizontal { @@ -271,8 +278,9 @@ open class MessageContentCell: MessageCollectionViewCell { /// Positions the cell's accessory view. /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell. open func layoutAccessoryView(with attributes: MessagesCollectionViewLayoutAttributes) { - var y = (bounds.height - attributes.accessoryViewSize.height) / 2 - y -= attributes.accessoryViewPadding.vertical + attributes.accessoryViewPadding.top + + // Accessory view aligned to the middle of the messageContainerView + let y = messageContainerView.frame.midY - (attributes.accessoryViewSize.height / 2) var origin = CGPoint(x: 0, y: y)