Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'main'
# Conflicts:
#	Sources/Layout/MessageSizeCalculator.swift
#	Sources/Views/MessageLabel.swift
  • Loading branch information
RomanPodymov committed Oct 20, 2023
commit 8051c83ee95bd5f83b7d52e08ccb5c807c7373eb
132 changes: 7 additions & 125 deletions Sources/Layout/MessageSizeCalculator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,25 +202,11 @@ open class MessageSizeCalculator: CellSizeCalculator {
return CGSize(width: messagesLayout.itemWidth, height: height)
}

public var incomingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(left: 42))
public var outgoingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(right: 42))

private lazy var textContainer: NSTextContainer = {
let textContainer = NSTextContainer()
textContainer.maximumNumberOfLines = 0
textContainer.lineFragmentPadding = 0
return textContainer
}()
private lazy var layoutManager: NSLayoutManager = {
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
return layoutManager
}()
private lazy var textStorage: NSTextStorage = {
let textStorage = NSTextStorage()
textStorage.addLayoutManager(layoutManager)
return textStorage
}()
open func cellBottomLabelAlignment(for message: MessageType) -> LabelAlignment {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingCellBottomLabelAlignment : incomingCellBottomLabelAlignment
}

// MARK: - Bottom Message Label

Expand Down Expand Up @@ -338,112 +324,8 @@ open class MessageSizeCalculator: CellSizeCalculator {
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil).integral

public func avatarSize(for message: MessageType) -> CGSize {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingAvatarSize : incomingAvatarSize
}

// MARK: - Top cell Label

public func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
let layoutDelegate = messagesLayout.messagesLayoutDelegate
let collectionView = messagesLayout.messagesCollectionView
let height = layoutDelegate.cellTopLabelHeight(for: message, at: indexPath, in: collectionView)
return CGSize(width: messagesLayout.itemWidth, height: height)
}

public func cellTopLabelAlignment(for message: MessageType) -> LabelAlignment {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingCellTopLabelAlignment : incomingCellTopLabelAlignment
}

// MARK: - Top message Label

public func messageTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
let layoutDelegate = messagesLayout.messagesLayoutDelegate
let collectionView = messagesLayout.messagesCollectionView
let height = layoutDelegate.messageTopLabelHeight(for: message, at: indexPath, in: collectionView)
return CGSize(width: messagesLayout.itemWidth, height: height)
}

public func messageTopLabelAlignment(for message: MessageType) -> LabelAlignment {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingMessageTopLabelAlignment : incomingMessageTopLabelAlignment
}

// MARK: - Bottom Label

public func messageBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
let layoutDelegate = messagesLayout.messagesLayoutDelegate
let collectionView = messagesLayout.messagesCollectionView
let height = layoutDelegate.messageBottomLabelHeight(for: message, at: indexPath, in: collectionView)
return CGSize(width: messagesLayout.itemWidth, height: height)
}

public func messageBottomLabelAlignment(for message: MessageType) -> LabelAlignment {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingMessageBottomLabelAlignment : incomingMessageBottomLabelAlignment
}

// MARK: - Accessory View

public func accessoryViewSize(for message: MessageType) -> CGSize {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingAccessoryViewSize : incomingAccessoryViewSize
}

public func accessoryViewPadding(for message: MessageType) -> HorizontalEdgeInsets {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingAccessoryViewPadding : incomingAccessoryViewPadding
}

// MARK: - MessageContainer

public func messageContainerPadding(for message: MessageType) -> UIEdgeInsets {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingMessagePadding : incomingMessagePadding
}

open func messageContainerSize(for message: MessageType) -> CGSize {
// Returns .zero by default
return .zero
}

open func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
let avatarWidth = avatarSize(for: message).width
let messagePadding = messageContainerPadding(for: message)
let accessoryWidth = accessoryViewSize(for: message).width
let accessoryPadding = accessoryViewPadding(for: message)
return messagesLayout.itemWidth - avatarWidth - messagePadding.horizontal - accessoryWidth - accessoryPadding.horizontal
}

// MARK: - Helpers

public var messagesLayout: MessagesCollectionViewFlowLayout {
guard let layout = layout as? MessagesCollectionViewFlowLayout else {
fatalError("Layout object is missing or is not a MessagesCollectionViewFlowLayout")
}
return layout
}

internal func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize {
let constraintBox = CGSize(width: maxWidth, height: .greatestFiniteMagnitude)

textContainer.size = constraintBox
textStorage.replaceCharacters(in: NSRange(location: 0, length: textStorage.length), with: attributedText)
layoutManager.ensureLayout(for: textContainer)

let size = layoutManager.usedRect(for: textContainer).size

return CGSize(width: size.width.rounded(.up), height: size.height.rounded(.up))
}
return rect.size
}
}

extension UIEdgeInsets {
Expand Down
112 changes: 4 additions & 108 deletions Sources/Views/MessageLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,114 +293,10 @@ open class MessageLabel: UILabel {
for detector in detectors {
guard let rangeTuples = rangesForDetectors[detector] else { continue }

private var attributesNeedUpdate = false

public static var defaultAttributes: [NSAttributedString.Key: Any] = {
return [
NSAttributedString.Key.foregroundColor: UIColor.darkText,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.underlineColor: UIColor.darkText
]
}()

open internal(set) var addressAttributes: [NSAttributedString.Key: Any] = defaultAttributes

open internal(set) var dateAttributes: [NSAttributedString.Key: Any] = defaultAttributes

open internal(set) var phoneNumberAttributes: [NSAttributedString.Key: Any] = defaultAttributes

open internal(set) var urlAttributes: [NSAttributedString.Key: Any] = defaultAttributes

open internal(set) var transitInformationAttributes: [NSAttributedString.Key: Any] = defaultAttributes

public func setAttributes(_ attributes: [NSAttributedString.Key: Any], detector: DetectorType) {
switch detector {
case .phoneNumber:
phoneNumberAttributes = attributes
case .address:
addressAttributes = attributes
case .date:
dateAttributes = attributes
case .url:
urlAttributes = attributes
case .transitInformation:
transitInformationAttributes = attributes
}
if isConfiguring {
attributesNeedUpdate = true
} else {
updateAttributes(for: [detector])
}
}

// MARK: - Initializers

public override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}

public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}

// MARK: - Open Methods

open override func drawText(in rect: CGRect) {

let insetRect = rect.inset(by: textInsets)
textContainer.size = CGSize(width: insetRect.width, height: insetRect.height)

let origin = insetRect.origin
let range = layoutManager.glyphRange(for: textContainer)

layoutManager.drawBackground(forGlyphRange: range, at: origin)
layoutManager.drawGlyphs(forGlyphRange: range, at: origin)
}

// MARK: - Public Methods

public func configure(block: () -> Void) {
isConfiguring = true
block()
if attributesNeedUpdate {
updateAttributes(for: enabledDetectors)
}
attributesNeedUpdate = false
isConfiguring = false
setNeedsDisplay()
}

// MARK: - Private Methods

private func setTextStorage(_ newText: NSAttributedString?, shouldParse: Bool) {

guard let newText = newText, newText.length > 0 else {
textStorage.setAttributedString(NSAttributedString())
setNeedsDisplay()
return
}

let style = paragraphStyle(for: newText)
let range = NSRange(location: 0, length: newText.length)

let mutableText = NSMutableAttributedString(attributedString: newText)
mutableText.addAttribute(.paragraphStyle, value: style, range: range)

if shouldParse {
rangesForDetectors.removeAll()
let results = parse(text: mutableText)
setRangesForDetectors(in: results)
}

for (detector, rangeTuples) in rangesForDetectors {
if enabledDetectors.contains(detector) {
let attributes = detectorAttributes(for: detector)
rangeTuples.forEach { (range, _) in
mutableText.addAttributes(attributes, range: range)
}
}
for (range, _) in rangeTuples {
// This will enable us to attribute it with our own styles, since `UILabel` does not provide link attribute overrides like `UITextView` does
if detector.textCheckingType == .link {
mutableAttributedString.removeAttribute(NSAttributedString.Key.link, range: range)
}

let attributes = detectorAttributes(for: detector)
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.