Add customizable accessoryView besides messageContainer#710
Add customizable accessoryView besides messageContainer#710SD10 merged 13 commits intoMessageKit:developmentfrom hyouuu:development
Conversation
| /// - 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) |
There was a problem hiding this comment.
I don't think this method fits well with this pull request 🤔
There was a problem hiding this comment.
Well I know it feels too flexible, but couldn't figure out another way to customize an accessory view - thoughts?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Maybe we could add a method to configure the accessory view just like we're doing for the avatar 🤔
There was a problem hiding this comment.
Let me try to mimic the way avatar is doing
| cellTopLabel.text = nil | ||
| messageTopLabel.text = nil | ||
| messageBottomLabel.text = nil | ||
| for view in accessoryView.subviews { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Doesn't the accessoryViewPadding allow the user to adjust the y position here?
There was a problem hiding this comment.
Good point. Will update
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I guess the delegate calls should be different by indexPath's section or item? I could make the change
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@zhongwuzw what if we just make the default size for it .zero?
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
Maybe later we would add more feature views, I don't think we add them all on cell is a good approach. 😂 .
There was a problem hiding this comment.
@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 |
There was a problem hiding this comment.
Padding would definitely need to be defined through the cell calculator objects and not via delegate
|
@SD10 updated and now it looks much like avatarView :D Let me know what you think! |
|
@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() |
There was a problem hiding this comment.
Can you change this to open var accessoryView: UView = UIView()?
|
In addition to the layout of the |
| /// Positions the cell's accessory view. | ||
| /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell. | ||
| open func layoutAccessoryView(with attributes: MessagesCollectionViewLayoutAttributes) { | ||
| guard attributes.accessoryViewSize != .zero else { return } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I removed this line alongside other zero checks - let me know if you want me to revert the change for others :)
|
@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 |
|
@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. |
|
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 ;) |
|
Turned out the failed test was not related to this PR, but that we had |
|
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) |
There was a problem hiding this comment.
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 } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
So, moved the initial bottomInset setting to viewDidAppear lol
There was a problem hiding this comment.
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
This reverts commit 081ca7a.
|
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 |
There was a problem hiding this comment.
y maybe should align to the center.y of the bubble?
cc @SD10
There was a problem hiding this comment.
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
There was a problem hiding this comment.
this also cannot go in 1.0, it is a 2.0 feature for sure
There was a problem hiding this comment.
Does bubble refer to messageContainerView? If so I could make the change
There was a problem hiding this comment.
@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
There was a problem hiding this comment.
@SD10 doesn't these lines https://github.com/MessageKit/MessageKit/pull/710/files#diff-ed5b75922e64407792c5a2dc5c27ebb6R107 cover the accessoryView height greater than the rest cases?
There was a problem hiding this comment.
@hyouuu You're right this could work, but I'll need to test to make sure
There was a problem hiding this comment.
Thanks! Let me know if there's anything I can help with :D
|
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) |
There was a problem hiding this comment.
@hyouuu You will have to pull in the latest changes from master. The CHANGELOG is outdated now
Merge from upstream
|
Ah sorry - should be good now @SD10 |
|
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! 😃 |
|
It's an honor!! Thank you :D |
|
@SD10 when do you think this feature will be merged with master ? |
|
@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. |
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: