123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- /*
- 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 UIKit
- import AVFoundation
- /// The layout object used by `MessagesCollectionView` to determine the size of all
- /// framework provided `MessageCollectionViewCell` subclasses.
- open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
- open override class var layoutAttributesClass: AnyClass {
- return MessagesCollectionViewLayoutAttributes.self
- }
-
- /// The `MessagesCollectionView` that owns this layout object.
- public var messagesCollectionView: MessagesCollectionView {
- guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
- fatalError(MessageKitError.layoutUsedOnForeignType)
- }
- return messagesCollectionView
- }
-
- /// The `MessagesDataSource` for the layout's collection view.
- public var messagesDataSource: MessagesDataSource {
- guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
- fatalError(MessageKitError.nilMessagesDataSource)
- }
- return messagesDataSource
- }
-
- /// The `MessagesLayoutDelegate` for the layout's collection view.
- public var messagesLayoutDelegate: MessagesLayoutDelegate {
- guard let messagesLayoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
- fatalError(MessageKitError.nilMessagesLayoutDelegate)
- }
- return messagesLayoutDelegate
- }
- public var itemWidth: CGFloat {
- guard let collectionView = collectionView else { return 0 }
- return collectionView.frame.width - sectionInset.left - sectionInset.right
- }
- // MARK: - Initializers
- public override init() {
- super.init()
- sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
- NotificationCenter.default.addObserver(self, selector: #selector(MessagesCollectionViewFlowLayout.handleOrientationChange(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
- }
- required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- deinit {
- NotificationCenter.default.removeObserver(self)
- }
- // MARK: - Attributes
- open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
- guard let attributesArray = super.layoutAttributesForElements(in: rect) as? [MessagesCollectionViewLayoutAttributes] else {
- return nil
- }
- for attributes in attributesArray where attributes.representedElementCategory == .cell {
- let cellSizeCalculator = cellSizeCalculatorForItem(at: attributes.indexPath)
- cellSizeCalculator.configure(attributes: attributes)
- }
- return attributesArray
- }
- open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
- guard let attributes = super.layoutAttributesForItem(at: indexPath) as? MessagesCollectionViewLayoutAttributes else {
- return nil
- }
- if attributes.representedElementCategory == .cell {
- let cellSizeCalculator = cellSizeCalculatorForItem(at: attributes.indexPath)
- cellSizeCalculator.configure(attributes: attributes)
- }
- return attributes
- }
- // MARK: - Layout Invalidation
- open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
- return collectionView?.bounds.width != newBounds.width
- }
- open override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
- let context = super.invalidationContext(forBoundsChange: newBounds)
- guard let flowLayoutContext = context as? UICollectionViewFlowLayoutInvalidationContext else { return context }
- flowLayoutContext.invalidateFlowLayoutDelegateMetrics = shouldInvalidateLayout(forBoundsChange: newBounds)
- return flowLayoutContext
- }
- @objc
- private func handleOrientationChange(_ notification: Notification) {
- invalidateLayout()
- }
- // MARK: - Cell Sizing
- lazy open var textMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
- lazy open var attributedTextMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
- lazy open var emojiMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
- lazy open var photoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
- lazy open var videoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
- lazy open var locationMessageSizeCalculator = LocationMessageSizeCalculator(layout: self)
- open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
- let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
- switch message.kind {
- case .text:
- return textMessageSizeCalculator
- case .attributedText:
- return attributedTextMessageSizeCalculator
- case .emoji:
- return emojiMessageSizeCalculator
- case .photo:
- return photoMessageSizeCalculator
- case .video:
- return videoMessageSizeCalculator
- case .location:
- return locationMessageSizeCalculator
- case .custom:
- fatalError("Must return a CellSizeCalculator for MessageKind.custom(Any?)")
- }
- }
- open func sizeForItem(at indexPath: IndexPath) -> CGSize {
- let calculator = cellSizeCalculatorForItem(at: indexPath)
- return calculator.sizeForItem(at: indexPath)
- }
-
- /// Set `incomingAvatarSize` of all `MessageSizeCalculator`s
- public func setMessageIncomingAvatarSize(_ newSize: CGSize) {
- messageSizeCalculators().forEach { $0.incomingAvatarSize = newSize }
- }
-
- /// Set `outgoingAvatarSize` of all `MessageSizeCalculator`s
- public func setMessageOutgoingAvatarSize(_ newSize: CGSize) {
- messageSizeCalculators().forEach { $0.outgoingAvatarSize = newSize }
- }
-
- /// Set `incomingAvatarPosition` of all `MessageSizeCalculator`s
- public func setMessageIncomingAvatarPosition(_ newPosition: AvatarPosition) {
- messageSizeCalculators().forEach { $0.incomingAvatarPosition = newPosition }
- }
-
- /// Set `outgoingAvatarPosition` of all `MessageSizeCalculator`s
- public func setMessageOutgoingAvatarPosition(_ newPosition: AvatarPosition) {
- messageSizeCalculators().forEach { $0.outgoingAvatarPosition = newPosition }
- }
-
- /// Set `incomingMessagePadding` of all `MessageSizeCalculator`s
- public func setMessageIncomingMessagePadding(_ newPadding: UIEdgeInsets) {
- messageSizeCalculators().forEach { $0.incomingMessagePadding = newPadding }
- }
-
- /// Set `outgoingMessagePadding` of all `MessageSizeCalculator`s
- public func setMessageOutgoingMessagePadding(_ newPadding: UIEdgeInsets) {
- messageSizeCalculators().forEach { $0.outgoingMessagePadding = newPadding }
- }
-
- /// Set `incomingCellTopLabelAlignment` of all `MessageSizeCalculator`s
- public func setMessageIncomingCellTopLabelAlignment(_ newAlignment: LabelAlignment) {
- messageSizeCalculators().forEach { $0.incomingCellTopLabelAlignment = newAlignment }
- }
-
- /// Set `outgoingCellTopLabelAlignment` of all `MessageSizeCalculator`s
- public func setMessageOutgoingCellTopLabelAlignment(_ newAlignment: LabelAlignment) {
- messageSizeCalculators().forEach { $0.outgoingCellTopLabelAlignment = newAlignment }
- }
-
- /// Set `incomingMessageTopLabelAlignment` of all `MessageSizeCalculator`s
- public func setMessageIncomingMessageTopLabelAlignment(_ newAlignment: LabelAlignment) {
- messageSizeCalculators().forEach { $0.incomingMessageTopLabelAlignment = newAlignment }
- }
-
- /// Set `outgoingMessageTopLabelAlignment` of all `MessageSizeCalculator`s
- public func setMessageOutgoingMessageTopLabelAlignment(_ newAlignment: LabelAlignment) {
- messageSizeCalculators().forEach { $0.outgoingMessageTopLabelAlignment = newAlignment }
- }
-
- /// Set `incomingMessageBottomLabelAlignment` of all `MessageSizeCalculator`s
- public func setMessageIncomingMessageBottomLabelAlignment(_ newAlignment: LabelAlignment) {
- messageSizeCalculators().forEach { $0.incomingMessageBottomLabelAlignment = newAlignment }
- }
-
- /// Set `outgoingMessageBottomLabelAlignment` of all `MessageSizeCalculator`s
- public func setMessageOutgoingMessageBottomLabelAlignment(_ newAlignment: LabelAlignment) {
- messageSizeCalculators().forEach { $0.outgoingMessageBottomLabelAlignment = newAlignment }
- }
-
- /// Get all `MessageSizeCalculator`s
- open func messageSizeCalculators() -> [MessageSizeCalculator] {
- return [textMessageSizeCalculator, attributedTextMessageSizeCalculator, emojiMessageSizeCalculator, photoMessageSizeCalculator, videoMessageSizeCalculator, locationMessageSizeCalculator]
- }
-
- }
|