mirror of
https://github.com/MessageKit/MessageKit.git
synced 2026-02-06 19:03:19 +00:00
168 lines
6.6 KiB
Swift
168 lines
6.6 KiB
Swift
/*
|
|
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
|
|
}
|
|
|
|
internal 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()
|
|
|
|
textMessageSizeCalculator.layout = self
|
|
attributedTextMessageSizeCalculator.layout = self
|
|
emojiMessageSizeCalculator.layout = self
|
|
photoMessageSizeCalculator.layout = self
|
|
videoMessageSizeCalculator.layout = self
|
|
locationMessageSizeCalculator.layout = self
|
|
|
|
sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(MessagesCollectionViewFlowLayout.handleOrientationChange(_:)), name: .UIDeviceOrientationDidChange, 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
|
|
|
|
var textMessageSizeCalculator = TextMessageSizeCalculator()
|
|
var attributedTextMessageSizeCalculator = TextMessageSizeCalculator()
|
|
var emojiMessageSizeCalculator = TextMessageSizeCalculator()
|
|
var photoMessageSizeCalculator = MediaMessageSizeCalculator()
|
|
var videoMessageSizeCalculator = MediaMessageSizeCalculator()
|
|
var locationMessageSizeCalculator = LocationMessageSizeCalculator()
|
|
|
|
open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> MessageSizeCalculator {
|
|
let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
|
|
switch message.data {
|
|
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 MessageData.custom(Any?)")
|
|
}
|
|
}
|
|
|
|
open func sizeForItem(at indexPath: IndexPath) -> CGSize {
|
|
let calculator = cellSizeCalculatorForItem(at: indexPath)
|
|
return calculator.sizeForItem(at: indexPath)
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
extension MessagesCollectionViewFlowLayout {
|
|
|
|
internal var messagesCollectionView: MessagesCollectionView {
|
|
guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
|
|
fatalError(MessageKitError.layoutUsedOnForeignType)
|
|
}
|
|
return messagesCollectionView
|
|
}
|
|
|
|
internal var messagesDataSource: MessagesDataSource {
|
|
guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
|
|
fatalError(MessageKitError.nilMessagesDataSource)
|
|
}
|
|
return messagesDataSource
|
|
}
|
|
|
|
internal var messagesLayoutDelegate: MessagesLayoutDelegate {
|
|
guard let messagesLayoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
|
|
fatalError(MessageKitError.nilMessagesLayoutDelegate)
|
|
}
|
|
return messagesLayoutDelegate
|
|
}
|
|
}
|