From 148b64c996d36ff64db9a123994b18fdd2bcf363 Mon Sep 17 00:00:00 2001 From: Diego Sanchez Date: Mon, 1 Feb 2016 11:20:19 +0000 Subject: [PATCH] Fixes scrollToBottom when typing very fast. Also fixes floating point rounding issue (it was showing on iPhone 6+) --- .../ChatViewController+Scrolling.swift | 24 +++++++++++++++---- .../ChatController/ChatViewController.swift | 5 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Chatto/Source/ChatController/ChatViewController+Scrolling.swift b/Chatto/Source/ChatController/ChatViewController+Scrolling.swift index 1bab97a..4fc9891 100644 --- a/Chatto/Source/ChatController/ChatViewController+Scrolling.swift +++ b/Chatto/Source/ChatController/ChatViewController+Scrolling.swift @@ -29,6 +29,10 @@ public enum CellVerticalEdge { case Bottom } +extension CGFloat { + static let bma_epsilon: CGFloat = 0.001 +} + extension ChatViewController { public func isScrolledAtBottom() -> Bool { @@ -56,13 +60,13 @@ extension ChatViewController { } public func isIndexPathVisible(indexPath: NSIndexPath, atEdge edge: CellVerticalEdge) -> Bool { - if let attributes = self.collectionView.layoutAttributesForItemAtIndexPath(indexPath) { + if let attributes = self.collectionView.collectionViewLayout.layoutAttributesForItemAtIndexPath(indexPath) { let visibleRect = self.visibleRect() let intersection = visibleRect.intersect(attributes.frame) if edge == .Top { - return intersection.minY == attributes.frame.minY + return CGFloat.abs(intersection.minY - attributes.frame.minY) < CGFloat.bma_epsilon } else { - return intersection.maxY == attributes.frame.maxY + return CGFloat.abs(intersection.maxY - attributes.frame.maxY) < CGFloat.bma_epsilon } } return false @@ -71,14 +75,24 @@ extension ChatViewController { public func visibleRect() -> CGRect { let contentInset = self.collectionView.contentInset let collectionViewBounds = self.collectionView.bounds - return CGRect(x: CGFloat(0), y: self.collectionView.contentOffset.y + contentInset.top, width: collectionViewBounds.width, height: min(self.collectionView.contentSize.height, collectionViewBounds.height - contentInset.top - contentInset.bottom)) + let contentSize = self.collectionView.collectionViewLayout.collectionViewContentSize() + return CGRect(x: CGFloat(0), y: self.collectionView.contentOffset.y + contentInset.top, width: collectionViewBounds.width, height: min(contentSize.height, collectionViewBounds.height - contentInset.top - contentInset.bottom)) } public func scrollToBottom(animated animated: Bool) { // Note that we don't rely on collectionView's contentSize. This is because it won't be valid after performBatchUpdates or reloadData // After reload data, collectionViewLayout.collectionViewContentSize won't be even valid, so you may want to refresh the layout manually let offsetY = max(-self.collectionView.contentInset.top, self.collectionView.collectionViewLayout.collectionViewContentSize().height - self.collectionView.bounds.height + self.collectionView.contentInset.bottom) - self.collectionView.setContentOffset(CGPoint(x: 0, y: offsetY), animated: animated) + + // Don't use setContentOffset(:animated). If animated, contentOffset property will be updated along with the animation for each frame update + // If a message is inserted while scrolling is happening (as in very fast typing), we want to take the "final" content offset (not the "real time" one) to check if we should scroll to bottom again + if animated { + UIView.animateWithDuration(self.constants.updatesAnimationDuration, animations: { () -> Void in + self.collectionView.contentOffset = CGPoint(x: 0, y: offsetY) + }) + } else { + self.collectionView.contentOffset = CGPoint(x: 0, y: offsetY) + } } public func scrollToPreservePosition(oldRefRect oldRefRect: CGRect?, newRefRect: CGRect?) { diff --git a/Chatto/Source/ChatController/ChatViewController.swift b/Chatto/Source/ChatController/ChatViewController.swift index c3f111e..1171d98 100644 --- a/Chatto/Source/ChatController/ChatViewController.swift +++ b/Chatto/Source/ChatController/ChatViewController.swift @@ -159,9 +159,10 @@ public class ChatViewController: UIViewController, UICollectionViewDataSource, U let newInsetBottom = self.constants.defaultContentInsets.bottom + inputHeightWithKeyboard let insetBottomDiff = newInsetBottom - self.collectionView.contentInset.bottom - let allContentFits = self.collectionView.bounds.height - newInsetBottom - (self.collectionView.contentSize.height + self.collectionView.contentInset.top) >= 0 + let contentSize = self.collectionView.collectionViewLayout.collectionViewContentSize() + let allContentFits = self.collectionView.bounds.height - newInsetBottom - (contentSize.height + self.collectionView.contentInset.top) >= 0 - let currentDistanceToBottomInset = max(0, self.collectionView.bounds.height - self.collectionView.contentInset.bottom - (self.collectionView.contentSize.height - self.collectionView.contentOffset.y)) + let currentDistanceToBottomInset = max(0, self.collectionView.bounds.height - self.collectionView.contentInset.bottom - (contentSize.height - self.collectionView.contentOffset.y)) let newContentOffsetY = self.collectionView.contentOffset.y + insetBottomDiff - currentDistanceToBottomInset self.collectionView.contentInset.bottom = newInsetBottom