ekazaev / ChatLayout

ChatLayout is an alternative solution to MessageKit. It uses custom UICollectionViewLayout to provide you full control over the presentation as well as all the tools available in UICollectionView. It supports dynamic cells and supplementary view sizes.
MIT License
898 stars 67 forks source link

CollectionView does not scroll to last item when not visible item #14

Closed deehegarty closed 3 years ago

deehegarty commented 3 years ago

Expected behaviour: When a user sends a message and taps on the send button, the should be a scroll to the last item in the CollectionView.

Actual behaviour: When a user sends a message and taps on the send button, the CollectionView will scroll to show the just top of the last item.

It appears to be taking the estimated item size instead of the actual size of the new cell that has just been sent by the end user. The cell is added to the view and can be seen once you manually scroll to the bottom. Example: when we increased the estimated item height to 100, the scrolling was correct, but only for smaller messages. If it was a bigger message, it would be cut off after 100 height.

Something to note is that the above behaviour occurs when animation = true when scrolling i.e. self?.collectionView.setContentOffset(contentOffsetAtBottom, animated: true). When animation = false the CollectionView will scroll to the correct position.

Reproduction steps:

  1. Open conversation
  2. Have at least enough messages where you can scroll up and not see the latest message
  3. Send a message while you are scrolled and can’t see the latest message
  4. See how it scrolls only to the top of the latest message not to the cell itself.

Note: scrollToBottom() is not currently working in the Example app provided

ekazaev commented 3 years ago

Hi @deehegarty

Thank you for the issue. Can you explain me some details. Current Example app does not scroll to the bottom of the collection view after the new message is sent. Or if it does - it is a side effect of some other action. Is that the issue within your modification?

Actual behaviour: When a user sends a message and taps on the send button, the CollectionView will scroll to show the just top of the last item.

ekazaev commented 3 years ago

I should give some details here. Whatever happens with layout items outside of visible area - they will always have an estimated value. Every collection layout behaves this way. The actual size of the cell will be calculated only at the moment of appearance of the cell. Just imagine the situation when you inserted not just 1 cell but 10000, but all of them are outside of the visible area. It will take severe amount of time to calculate 10000 layouts. But this calculations are completely unnecessary. So you can not rely on collectionView.setContentOffset(WHATEVER, animated: true) as the actual contentOffset of the cell that you expect to reach will be different. During the animated scrolling every cell that appears will be calculated and will change the contentSize. It applies to any collection view layout.

If you require to scroll to some specific cell - you should use func restoreContentOffset(with snapshot: ChatLayoutPositionSnapshot) provided with ChatLayout

So, in the current example app. If you require to scroll to the bottom of the collection view every time user sends a message - it needs to be modified this way:

            self.chatController.sendMessage(.text(messageText)) { sections in
                UIView.animate(withDuration: 0.25, animations: {
                    let positionSnapshot = ChatLayoutPositionSnapshot(indexPath: IndexPath(item: 0, section: 0), kind: .footer, edge: .bottom)
                    self.chatLayout.restoreContentOffset(with: positionSnapshot)
                }, completion: { _ in
                    self.currentInterfaceActions.options.remove(.sendingMessage)
                    self.processUpdates(with: sections, animated: true)
                })
            }

But, be aware: If you are using UIView.animate(...) as suggested, you may face the issues like one described here https://dasdom.dev/posts/scrolling-a-collection-view-with-custom-duration/ and you should implement your own animated scrolling method using CADisplayLink if you want everything to be smooth.

ekazaev commented 3 years ago

@deehegarty Please let me know if you are satisfied with the answer. If you have any other question - please do not hesitate to ask.

deehegarty commented 3 years ago

@ekazaev Thanks so much for this information (some really good info here). What I had inside public func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) was a call to scrollToBottom() instead of the code snippet you have just provided. I had thought that calling scrollToBottom() at this point would scroll to the last item in the CollectionView. What actually happens is the behaviour that I have described above.

ekazaev commented 3 years ago

@deehegarty I think its just a misleading name of the method. I use it only to adjust the offset the the size of input view changes, it usually happens within the visible zone, so for me those nuances are not critical. Ill update the Example app to avoid the confusion.

deehegarty commented 3 years ago

@ekazaev Thanks so much for the clarification 😃

ekazaev commented 3 years ago

@deehegarty JFYI: The example app was recently updated to demonstrate one of the possible implementations of your request