Skip to content

Add customizable accessoryView besides messageContainer#710

Merged
SD10 merged 13 commits intoMessageKit:developmentfrom
hyouuu:development
Jun 27, 2018
Merged

Add customizable accessoryView besides messageContainer#710
SD10 merged 13 commits intoMessageKit:developmentfrom
hyouuu:development

Conversation

@hyouuu
Copy link
Contributor

@hyouuu hyouuu commented Jun 2, 2018

Add an accessoryView and give client a chance to config it to whatever they like (e.g. a button, image)

Does this close any currently open issues?

Found one ;) #190

Any relevant logs, error output, etc?

NA

Any other comments?

Let me know how you want to approach it!
This was originally PR'ed for 0.12 at #432

Where has this been tested?

Devices/Simulators: iPhone X device; iPhone 8+ simulator

iOS Version: 11.4

Swift Version: 4.1

MessageKit Version: 1.0.0-beta.1

Example screenshot where in configCell added a UIView with green background:

screen shot 2017-12-31 at 1 01 22 pm

/// - message: The `MessageType` that will be displayed by this cell.
/// - indexPath: The `IndexPath` of the cell.
///
func configCell(_ cell: MessageContentCell, for message: MessageType, at indexPath: IndexPath)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this method fits well with this pull request 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well I know it feels too flexible, but couldn't figure out another way to customize an accessory view - thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

All this method does is adds a layer of indirection for cellForRowAtIndexPath. Everyone subclasses MessagesViewController to use the library. They should just override cellForRowAtIndexPath and do this themselves

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could add a method to configure the accessory view just like we're doing for the avatar 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me try to mimic the way avatar is doing

cellTopLabel.text = nil
messageTopLabel.text = nil
messageBottomLabel.text = nil
for view in accessoryView.subviews {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure about this approach, to be honest. I don't like adding or removing views from the view hierarchy in cellForRowAtIndexPath. I think if you need to do this, it's a sign you need more cells or a better abstraction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it is kind of arbitrary. Maybe providing a similar method to the configCell, a prepareForReuse call so client can do their customized preparation? Or better thoughts?

Copy link
Member

@SD10 SD10 Jun 2, 2018

Choose a reason for hiding this comment

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

I mean I really don't want to provide any method for this. Where would prepareForReuse get called? They can just override MessageContentCell and do this themselves. I don't think we should have a method in the controller handling preparing the reuse of cells....

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

let y = (bounds.height - attributes.accessoryViewSize.height) / 2
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't the accessoryViewPadding allow the user to adjust the y position here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Will update

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering what this looks like when we have varying label sizes, avatar sizes, etc. What would this look like if the avatar size is bigger than the message? What if there is a very large label? Lots of things to think about

///
/// - Note:
/// The default value returned by this method is zero.
func accessoryViewSize(for message: MessageType, in messagesCollectionView: MessagesCollectionView) -> CGSize
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if we want to make this a delegate method or to put it as a property of the layout object.
@zhongwuzw, this is a feature MessageKit should support so I'd like your advice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess the delegate calls should be different by indexPath's section or item? I could make the change

Copy link
Contributor Author

@hyouuu hyouuu Jun 2, 2018

Choose a reason for hiding this comment

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

Ah but by moving it to size calculator, we'd need another display delegate method like the

func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)

, and then do something like

if let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout {
           layout.textMessageSizeCalculator.incomingAvatarSize = CGSize(width: 52, height: 52)
           layout.textMessageSizeCalculator.outgoingAvatarSize = CGSize(width: 52, height: 52)
           layout.textMessageSizeCalculator.incomingAvatarPosition = AvatarPosition(vertical: .messageCenter)
           layout.textMessageSizeCalculator.outgoingAvatarPosition = AvatarPosition(vertical: .messageCenter)
       }

, which I personally feel pretty weird. Let me know what you think

Copy link
Member

Choose a reason for hiding this comment

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

The reason for this is I really wanted to move away from having users configure things with the delegates. I'd rather they abstract the logic for the layout of a cell into a CellSizeCalculator object. Then provide the layout object with the calculator.

IMO, doing it another way does not feel right. The API we're using here feels like it was designed for an earlier version of MessageKit and not version 1.0

Copy link
Member

Choose a reason for hiding this comment

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

@SD10 , My some thoughts, wether we can provide a flag to not use accessoryView, not just set size to .zero, because I think many of the time, maybe user don't need accessoryView at all.

Copy link
Member

Choose a reason for hiding this comment

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

@zhongwuzw what if we just make the default size for it .zero?

Copy link
Member

Choose a reason for hiding this comment

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

@SD10 , .zero can invisible to user, I mean it would leads to unnecessary calculations, render pass (yeah, it's true, though it's invisible to user, it still has these works). I'm a performancer 😢 .
I wonder wether we need add a boolean to control this.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe later we would add more feature views, I don't think we add them all on cell is a good approach. 😂 .

Copy link
Member

@SD10 SD10 Jun 4, 2018

Choose a reason for hiding this comment

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

@zhongwuzw this is true 👍 but I've learned not to prematurely optimize. I only optimize if someone complains and then if we do benchmarks afterward. We do cell layout using frames and not auto-layout, so we already save so much. This is likely the last view we will support. I will not add views that don't exist in JSQMVC

///
/// - Note:
/// The default value returned by this method is zero.
func accessoryViewPadding(for message: MessageType, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets
Copy link
Member

Choose a reason for hiding this comment

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

Padding would definitely need to be defined through the cell calculator objects and not via delegate

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 3, 2018

@SD10 updated and now it looks much like avatarView :D Let me know what you think!

@SD10
Copy link
Member

SD10 commented Jun 3, 2018

@hyouuu Thanks for making these changes 💯 This is definitely heading in the right direction. I am still a little bit worried about the layout of the accessory view, that is something I'll need to test and review in more detail.

}()

// Should only add customized subviews - don't change accessoryView itself.
open var accessoryView: UIView = .init()
Copy link
Member

Choose a reason for hiding this comment

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

Can you change this to open var accessoryView: UView = UIView()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure

@SD10
Copy link
Member

SD10 commented Jun 3, 2018

In addition to the layout of the accessoryView probably not being correct, this also changes the height calculations for the size of the cell. There are some scenarios where someone could set an accessoryView that is taller than the rest of the message content.

/// Positions the cell's accessory view.
/// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
open func layoutAccessoryView(with attributes: MessagesCollectionViewLayoutAttributes) {
guard attributes.accessoryViewSize != .zero else { return }
Copy link
Member

Choose a reason for hiding this comment

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

We should remove this line and set the frame regardless. Since cells are re-used, this will cause bugs. I need to fix these on the other layout methods in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this line alongside other zero checks - let me know if you want me to revert the change for others :)

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 4, 2018

@zhongwuzw for some reason I couldn't find the comments you 2 added (but received emails) on GitHub :/

I'm on the side with @SD10 that we probably don't want to optimize for the performance of this specific case at the moment, but later on we could add more flexible switches not only for accessoryView, but also for cellTopLabel & cellBottomLabel, which I guess many people don't need either

@SD10
Copy link
Member

SD10 commented Jun 4, 2018

@hyouuu That's a good suggestion, but as I said to @zhongwuzw I'm really not concerned. In previous versions of MessageKit it was taking ~6 seconds to lay out 10,000 cells. We've decreased that to probably around ~1 second. Realistically, no one should have that many cells on screen at a time. So until we have people complaining about performance, I'm not going to prematurely optimize anything

PS. You can probably find the comments from @zhongwuzw in the code review above. Hit "show outdated" from the drop down.

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 4, 2018

Regarding the tests, do you use Carthage to install Quick etc? Haven't used it and couldn't build the tests directly. Let me know if you want to test it yourself and correct the tests or want me to do it ;)

@SD10
Copy link
Member

SD10 commented Jun 4, 2018

Yeah @hyouuu we use Carthage to install the test dependencies. You’ll need to run carthage bootstrap

Sent with GitHawk

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 4, 2018

Turned out the failed test was not related to this PR, but that we had .natural for avatar horizontal position, so set a default .cellLeading; also fixed a few formatting things from warnings

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 5, 2018

Added one more commit to fix keyboard frame & insets as I was facing issues in my app - let me know if you actually want it to be in a separate PR - if so I'll remove it from my current fork right before we can push the current PR (as I need to use my fork to keep things working ;)


internal func addKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleKeyboardDidChangeState(_:)), name: .UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleKeyboardDidChangeState(_:)), name: .UIKeyboardDidChangeFrame, object: nil)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: only listening to UIKeyboardWillChangeFrame will cause issues in certain situations where keyboard was in the process to hide/show and then we get into our msgCollectionView, then we missed the willChange event and didn't catch up with didChange event

/// - Returns: The safeAreaInsets.bottom if its an iPhone with safe areas (e.g. X), else 0
internal var safeAreaBottomInset: CGFloat {
if #available(iOS 11.0, *) {
guard UIScreen.main.nativeBounds.height == 2436 else { return 0 }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This doesn't make sense and is hacky - if a device has safe bottom, then we should just use it, no matter if it's an iPhone X or a future iPhone X+ or a Safe iPad

Copy link
Member

Choose a reason for hiding this comment

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

It is a hack but the logic you changed it to doesn’t work

// Hack to prevent animation of the contentInset after viewDidAppear
if isFirstLayout {
defer { isFirstLayout = false }
addKeyboardObservers()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we unobserve in deinit, we should pair and start observation in init or viewDidLoad - not sure why we would want to do it here, so corrected

Copy link
Member

Choose a reason for hiding this comment

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

I can’t really see the diff well for this in GitHawk, but certain keyboard events we only observe once to get the behavior we want


open override func viewDidLayoutSubviews() {
// Hack to prevent animation of the contentInset after viewDidAppear
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, moved the initial bottomInset setting to viewDidAppear lol

Copy link
Member

Choose a reason for hiding this comment

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

This is too late to call it. It won’t look how I want it. I’d appreciate it if you keep this PR in scope. It makes it hard to approve because now I can reject it for keyboard related things

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 5, 2018

Cool I reverted the changes in this branch, and will use another one for my project atm :) Let's figure that issue out after this one @SD10

/// - 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
Copy link
Member

Choose a reason for hiding this comment

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

y maybe should align to the center.y of the bubble?
cc @SD10

Copy link
Member

Choose a reason for hiding this comment

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

yeah @zhongwuzw this is one of the reasons why I'm delaying this. everything seems to look good except the calculations for where it is located. we also have to think about having a large accessoryView but then the rest of the cell content is very small

Copy link
Member

Choose a reason for hiding this comment

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

this also cannot go in 1.0, it is a 2.0 feature for sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does bubble refer to messageContainerView? If so I could make the change

Copy link
Member

@SD10 SD10 Jun 7, 2018

Choose a reason for hiding this comment

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

@hyouuu it's not necessarily that simple. what if the accessoryView is set to a height of 30pts, but the size of the other labels + the size of the message is only 20 points? if you try and center it to the messageContainerView your accessoryView will now leak out of the cell. there are a lot of edge cases here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@SD10 doesn't these lines https://github.com/MessageKit/MessageKit/pull/710/files#diff-ed5b75922e64407792c5a2dc5c27ebb6R107 cover the accessoryView height greater than the rest cases?

Copy link
Member

Choose a reason for hiding this comment

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

@hyouuu You're right this could work, but I'll need to test to make sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Let me know if there's anything I can help with :D

Copy link
Member

@SD10 SD10 left a comment

Choose a reason for hiding this comment

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

@hyouuu Can you please add a CHANGELOG entry for this under the Added section?

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 27, 2018

Done! @SD10

CHANGELOG.md Outdated
- Added convenience method to set properties for all `MessageSizeCalculator`s.
[#697](https://github.com/MessageKit/MessageKit/pull/697) by [@zhongwuzw](https://github.com/zhongwuzw).

- Added customizable `accessoryView`, with a new `MessagesDisplayDelegate` function `configureAccessoryView`, and corresponding size & padding properties in `MessageSizeCalculator`. [#710](https://github.com/MessageKit/MessageKit/pull/710) by [@hyouuu](https://github.com/hyouuu)
Copy link
Member

Choose a reason for hiding this comment

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

@hyouuu You will have to pull in the latest changes from master. The CHANGELOG is outdated now

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 27, 2018

Ah sorry - should be good now @SD10

@SD10 SD10 merged commit fab22c5 into MessageKit:development Jun 27, 2018
@SD10
Copy link
Member

SD10 commented Jun 27, 2018

Thank you for contributing to MessageKit! I've invited you to join the MessageKit GitHub organization - no pressure to accept! If you'd like more information on what that means, check out our contributing guidelines and join the MessageKit Slack channel. Feel free to reach out if you have any questions! 😃

@hyouuu
Copy link
Contributor Author

hyouuu commented Jun 27, 2018

It's an honor!! Thank you :D

@charvoa
Copy link

charvoa commented Jun 28, 2018

@SD10 when do you think this feature will be merged with master ?

@SD10
Copy link
Member

SD10 commented Jun 28, 2018

@charvoa Not sure unfortunately. A rough estimation would be 2-4 weeks. Right now my company is using a fork of MessageKit. That's why development has been so slow lately. Next week we're going to try to get off the fork and back to the main project.

@SD10 SD10 mentioned this pull request Aug 15, 2018
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants