-
-
Save mmick66/9812223 to your computer and use it in GitHub Desktop.
#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 |
Here you go - fixed the last cell issue ( it needed some "<=" & ">=" love) I incorporated what everyone added so this should center the middle element and keep it centered at the ends using insets.
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if let collectionView = collectionView,
first = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)),
last = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: collectionView.numberOfItemsInSection(0) - 1, inSection: 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.layoutAttributesForElementsInRect(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.targetContentOffsetForProposedContentOffset(proposedContentOffset)
}
}
Is there a way to have this and only move one page at a time like pagingEnabled?
How could i make it circular or infinite with item at centre?
@DavidSchechter did you find any solution for your concern ????
@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?
@mmick66 : I used your given solution for collection view.
But its scrolling didn't stop, it scrolls like there is no paging enabled.