Last active
August 2, 2022 10:06
-
-
Save mmick66/9812223 to your computer and use it in GitHub Desktop.
UICollectionViewFlowLayout with arbitrary sized Paging
This file contains hidden or 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 "UICollectionViewFlowLayoutCenterItem.h" | |
@implementation UICollectionViewFlowLayoutCenterItem | |
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity | |
{ | |
CGSize collectionViewSize = self.collectionView.bounds.size; | |
CGFloat proposedContentOffsetCenterX = proposedContentOffset.x + self.collectionView.bounds.size.width * 0.5f; | |
CGRect proposedRect = self.collectionView.bounds; | |
// Comment out if you want the collectionview simply stop at the center of an item while scrolling freely | |
// proposedRect = CGRectMake(proposedContentOffset.x, 0.0, collectionViewSize.width, collectionViewSize.height); | |
UICollectionViewLayoutAttributes* candidateAttributes; | |
for (UICollectionViewLayoutAttributes* attributes in [self layoutAttributesForElementsInRect:proposedRect]) | |
{ | |
// == Skip comparison with non-cell items (headers and footers) == // | |
if (attributes.representedElementCategory != UICollectionElementCategoryCell) | |
{ | |
continue; | |
} | |
// == First time in the loop == // | |
if(!candidateAttributes) | |
{ | |
candidateAttributes = attributes; | |
continue; | |
} | |
if (fabsf(attributes.center.x - proposedContentOffsetCenterX) < fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) | |
{ | |
candidateAttributes = attributes; | |
} | |
} | |
return CGPointMake(candidateAttributes.center.x - self.collectionView.bounds.size.width * 0.5f, proposedContentOffset.y); | |
} | |
@end |
@mcginnik I loved your answer, just converted it to Swift 3. Working perfect for me.
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if let collectionView = collectionView,
let first = layoutAttributesForItem(at: IndexPath(row: 0, section: 0)),
let last = layoutAttributesForItem(at: IndexPath(row: collectionView.numberOfItems(inSection: 0) - 1, section: 0))
{
sectionInset = UIEdgeInsets(top: 0, left: collectionView.frame.width / 2 - first.bounds.size.width / 2, bottom: 0, right: collectionView.frame.width / 2 - last.bounds.size.width / 2)
}
let collectionViewSize = self.collectionView!.bounds.size
let proposedContentOffsetCenterX = proposedContentOffset.x + collectionViewSize.width * 0.5
var proposedRect = self.collectionView!.bounds
// comment this out if you don't want it to scroll so quickly
proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionViewSize.width, height: collectionViewSize.height)
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in self.layoutAttributesForElements(in: proposedRect)! {
// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != .cell {
continue
}
// Get collectionView current scroll position
let currentOffset = self.collectionView!.contentOffset
// Don't even bother with items on opposite direction
// You'll get at least one, or else the fallback got your back
if (attributes.center.x <= (currentOffset.x + collectionViewSize.width * 0.5) && velocity.x > 0) || (attributes.center.x >= (currentOffset.x + collectionViewSize.width * 0.5) && velocity.x < 0) {
continue
}
// First good item in the loop
if candidateAttributes == nil {
candidateAttributes = attributes
continue
}
// Save constants to improve readability
let lastCenterOffset = candidateAttributes!.center.x - proposedContentOffsetCenterX
let centerOffset = attributes.center.x - proposedContentOffsetCenterX
if fabsf( Float(centerOffset) ) < fabsf( Float(lastCenterOffset) ) {
candidateAttributes = attributes
}
}
if candidateAttributes != nil {
// Great, we have a candidate
return CGPoint(x: candidateAttributes!.center.x - collectionViewSize.width * 0.5, y: proposedContentOffset.y)
} else {
// Fallback
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
}
}
Thanks!
Hi!
Loved the @efremidze answer.
I simple remove "paging snap", and now collection view will move on centered element due several pages:
`
class CollectionViewFlowLayoutCenterItem: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint,
withScrollingVelocity velocity: CGPoint) -> CGPoint {
var result = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
guard let collectionView = collectionView else {
return result
}
let halfWidth = 0.5 * collectionView.bounds.size.width
let proposedContentCenterX = result.x + halfWidth
let targetRect = CGRect(origin: result, size: collectionView.bounds.size)
let layoutAttributes = layoutAttributesForElements(in: targetRect)?
.filter { $0.representedElementCategory == .cell }
.sorted { abs($0.center.x - proposedContentCenterX) < abs($1.center.x - proposedContentCenterX) }
guard let closest = layoutAttributes?.first else {
return result
}
result = CGPoint(x: closest.center.x - halfWidth, y: proposedContentOffset.y)
return result
}
}
`
@iniko1983x's solution works very nicely, although both the first element and last elements are centred instead of left and right aligned respectively
Maybe some one need
override func layoutSubviews() {
super.layoutSubviews()
if
let first = collectionView.layoutAttributesForItem(at: IndexPath(item: 0, section: 0)),
let last = collectionView.layoutAttributesForItem(at: IndexPath(item: collectionView.numberOfItems(inSection: 0) - 1, section: 0)) {
let left = collectionView.frame.width / 2 - first.bounds.size.width / 2
let right = collectionView.frame.width / 2 - last.bounds.size.width / 2
collectionView.contentInset = UIEdgeInsets(top: 0.0, left: left, bottom: 0.0, right: right)
}
self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast
}
class CenterCellCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let collectionViewSize = self.collectionView!.bounds.size
let proposedContentOffsetCenterX = proposedContentOffset.x + collectionViewSize.width * 0.5
var proposedRect = self.collectionView!.bounds
// comment this out if you don't want it to scroll so quickly
proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionViewSize.width, height: collectionViewSize.height)
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in self.layoutAttributesForElements(in: proposedRect)! {
// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != .cell {
continue
}
// Get collectionView current scroll position
let currentOffset = self.collectionView!.contentOffset
// Don't even bother with items on opposite direction
// You'll get at least one, or else the fallback got your back
// swiftlint:disable:next line_length
if (attributes.center.x <= (currentOffset.x + collectionViewSize.width * 0.5) && velocity.x > 0) || (attributes.center.x >= (currentOffset.x + collectionViewSize.width * 0.5) && velocity.x < 0) {
continue
}
// First good item in the loop
if candidateAttributes == nil {
candidateAttributes = attributes
continue
}
// Save constants to improve readability
let lastCenterOffset = candidateAttributes!.center.x - proposedContentOffsetCenterX
let centerOffset = attributes.center.x - proposedContentOffsetCenterX
if fabsf( Float(centerOffset) ) < fabsf( Float(lastCenterOffset) ) {
candidateAttributes = attributes
}
}
if candidateAttributes != nil {
// Great, we have a candidate
return CGPoint(x: candidateAttributes!.center.x - collectionViewSize.width * 0.5, y: proposedContentOffset.y)
} else {
// Fallback
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
}
}
}
@DavidSchechter did you found the solution for scrolling one cell at a time using this? or anyone else?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@DavidSchechter did you find any solution for your concern ????