Skip to content

Comments

Open up MessagesCollectionViewFlowLayout cell sizing API#491

Merged
SD10 merged 10 commits intodevelopmentfrom
enhancement/custom-cell
Jan 31, 2018
Merged

Open up MessagesCollectionViewFlowLayout cell sizing API#491
SD10 merged 10 commits intodevelopmentfrom
enhancement/custom-cell

Conversation

@SD10
Copy link
Member

@SD10 SD10 commented Jan 28, 2018

Summary of Pull Request:

Resolves: #489, #487, #453, #396, #331, #234

open func layoutAvatarView(with attributes: MessagesCollectionViewLayoutAttributes)
open func layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes)
open func layoutTopLabel(with attributes: MessagesCollectionViewLayoutAttributes)
open func layoutBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes)

This allows users to control the layout of subviews by an override to one of these methods. Theoretically, they could override and make the functions a no-op, handling layout with something like autolayout.

  • Opens up cell sizing API
// MARK: - MessageContainerView
open func messageLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets
open func messageContainerPadding(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets
open func messageContainerMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat 
open func messageContainerSize(for message: MessageType, at indexPath: IndexPath) -> CGSize

// MARK: - Cell Top Label
open func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment
open func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize
open func cellTopLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat

// MARK: - Cell Bottom Label
open func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment
open func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize
open func cellBottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat

// MARK: - AvatarView
open func avatarPosition(for message: MessageType, at indexPath: IndexPath) -> AvatarPosition
open func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize

// MARK: - Cell Size
open func cellContentHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat
open func sizeForItem(at indexPath: IndexPath) -> CGSize

This also allows the MessagesCollectionViewFlowLayout to become more static. While a lot of these open methods will call into MessagesLayoutDelegate to retrieve the values, you can override the method that wraps the delegate call and return a static value instead.

  • Replaces MessageIntermediateLayoutAttributes with MessageCellLayoutContext

This is a lighter weight type and caches less information #489. We no longer hold a reference to any IndexPath or MessageType. We also no longer cache cell subview origins since the layout is now handled by the MessageCollectionViewCell. This fixes the annoying frame caching of cellTopLabel and cellBottomLabel in #234 and makes cell attribute caching much more useful again.

I expect that I messed at least one thing up with these changes. It will need some review. However, this API should be far easier to test now considering most things have been changed from private to internal or open.

TODO:

  • CHANGELOG
  • Fix tests

@SD10 SD10 changed the base branch from master to development January 28, 2018 19:26
@SD10 SD10 added this to the 0.14.0 milestone Jan 28, 2018
/// - Parameters:
/// - attributedText: The `NSAttributedString` used to calculate a size that fits.
/// - maxWidth: The max width available for the label.
internal func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These may be able to be combined into a single method for NSAttributedString


var avatarFrame: CGRect = .zero
var avatarSize: CGSize = .zero
var avatarPosition = AvatarPosition(vertical: .cellBottom)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to maybe use IUO's here

messageContainerView.frame = attributes.messageContainerFrame
}
guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else { return }
layoutMessageContainerView(with: attributes)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment that this has to be called first


// MARK: - Origin Calculations

func layoutAvatarView(with attributes: MessagesCollectionViewLayoutAttributes) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open

avatarView.frame = CGRect(origin: origin, size: attributes.avatarSize)
}

func layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open

messageContainerView.frame = CGRect(origin: origin, size: attributes.messageContainerSize)
}

func layoutTopLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open

cellTopLabel.frame = CGRect(origin: origin, size: topLabelSize)
}

func layoutBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open

@SD10
Copy link
Member Author

SD10 commented Jan 28, 2018

It's possible that none of these need to be open. They are already configured using the delegate. Having two would just cause confusion. I'll see if they have any benefits or can be removed.

open func avatarPosition(for message: MessageType, at indexPath: IndexPath) -> AvatarPosition
open func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment
open func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath) -> LabelAlignment
open func messageLabelInsets(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets
open func messageContainerPadding(for message: MessageType, at indexPath: IndexPath) -> UIEdgeInsets


/// A intermediate context used to store recently calculated values used by
/// the `MessagesCollectionViewFlowLayout` object to reduce redundant calculations.
final class MessageIntermediateLayoutAttributes {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RIP 💀

let avatarVertical = avatarPosition.vertical

switch (labelAlignment, avatarHorizontal) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fancy

open func layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes) {
guard attributes.messageContainerSize != .zero else { return }

var origin: CGPoint = .zero
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var origin = CGPoint.zero for consistency

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll flip flop them. I like explicitly typed better 👍

let bottomLabelPadding = bottomLabelAlignment.insets
let bottomLabelSize = attributes.bottomLabelSize

var origin: CGPoint = .zero
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var origin = CGPoint.zero for consistency

}
return messagesCollectionView
}
fileprivate var layoutContextCache: [MessageID: MessageCellLayoutContext] = [:]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to choose NSCache , it can control the number of object in memory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No layoutContextCache empty operation when prepareLayout?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's necessary? I don't even use the prepareLayout method 🤔

Copy link
Member

@zhongwuzw zhongwuzw Jan 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SD10 , I think it's necessary, dictionary has two weakness, one is occupy memory, another is performance penalty , the more number of data, the more time consumption when get object because of hash conflict.

Yep, we don't use prepareLayout, I mean we should override it that prepare something like empty cache when layout needs recalculation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently empty the cache inside of here:

open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {

I'm fine with changing this to a more efficient cache 👍

@@ -0,0 +1,21 @@
//
// UIEdgeInsets+Extensions.swift
// MessageKit
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with LICENSE

SD10 added a commit that referenced this pull request Jan 31, 2018
@SD10 SD10 force-pushed the enhancement/custom-cell branch from 40eb589 to f77100c Compare January 31, 2018 06:09
@SD10 SD10 merged commit 1d660b0 into development Jan 31, 2018
@SD10 SD10 deleted the enhancement/custom-cell branch January 31, 2018 07:23
@SD10 SD10 removed this from the 0.14.0 milestone Mar 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants