Created
June 6, 2018 03:14
-
-
Save GeekTree0101/12ea397c5142c97d99b24494774a9557 to your computer and use it in GitHub Desktop.
Texture Chat Application UICollectionView Flow Layout Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import AsyncDisplayKit | |
class ChatFlowLayoutExample: UICollectionViewFlowLayout { | |
private var topVisibleItem = Int.max | |
private var bottomVisibleItem = -Int.max | |
private var offset: CGFloat = 0.0 | |
private var visibleAttributes: [UICollectionViewLayoutAttributes]? | |
private var isPrepend: Bool = false | |
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { | |
// Reset offset and prepend scope | |
visibleAttributes = super.layoutAttributesForElements(in: rect) | |
offset = 0.0 | |
isPrepend = false | |
return visibleAttributes | |
} | |
override open func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { | |
guard let collectionView = self.collectionView else { return } | |
guard let visibleAttributes = self.visibleAttributes else { return } | |
// Calculate Bottom and Top Visible Item Count | |
bottomVisibleItem = -Int.max | |
topVisibleItem = Int.max | |
var containerHeight: CGFloat = collectionView.frame.size.height | |
containerHeight -= collectionView.contentInset.top | |
containerHeight -= collectionView.contentInset.bottom | |
let container = CGRect(x: collectionView.contentOffset.x, | |
y: collectionView.contentOffset.y, | |
width: collectionView.frame.size.width, | |
height: containerHeight) | |
for attributes in visibleAttributes { | |
if attributes.frame.intersects(container) { | |
let item = attributes.indexPath.item | |
if item < topVisibleItem { | |
topVisibleItem = item | |
} | |
if item > bottomVisibleItem { | |
bottomVisibleItem = item | |
} | |
} | |
} | |
super.prepare(forCollectionViewUpdates: updateItems) | |
// Check: Initial Load or Load More | |
let isInitialLoading: Bool = bottomVisibleItem + topVisibleItem == 0 | |
// Chack: Pre-Append or Append | |
if updateItems.first?.indexPathAfterUpdate?.item ?? -1 == 0, | |
updateItems.first?.updateAction == .insert, | |
!isInitialLoading { | |
self.isPrepend = true | |
} else { | |
return | |
} | |
// Calculate Offset | |
offset = updateItems.filter { $0.updateAction == .insert } | |
.map { $0.indexPathAfterUpdate }.filterNil() | |
.filter { topVisibleItem + updateItems.count > $0.item } | |
.map { self.layoutAttributesForItem(at: $0) }.filterNil() | |
.map { $0.size.height + self.minimumLineSpacing } | |
.reduce(0.0, { $0 + $1 }) | |
let contentHeight = collectionView.contentSize.height | |
var frameHeight = collectionView.frame.size.height | |
frameHeight -= collectionView.contentInset.top | |
frameHeight -= collectionView.contentInset.bottom | |
guard contentHeight + offset > frameHeight else { | |
// Exception | |
self.isPrepend = false | |
return | |
} | |
CATransaction.begin() | |
CATransaction.setDisableActions(true) | |
} | |
override open func finalizeCollectionViewUpdates() { | |
guard let collectionView = self.collectionView, isPrepend else { return } | |
// Adjust Content Offset | |
let newContentOffset = CGPoint(x: collectionView.contentOffset.x, | |
y: collectionView.contentOffset.y + self.offset) | |
collectionView.contentOffset = newContentOffset | |
CATransaction.commit() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment