UICollectionView horizontal paging with 3 items
Swift 3.0 Complete Solution based on Raheel Sadiq
var isfirstTimeTransform:Bool = true
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCustomViewCell", for: indexPath)
if (indexPath.row == 0 && isfirstTimeTransform) {
isfirstTimeTransform = false
}else{
cell.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width/3, height: collectionView.bounds.height)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Simulate "Page" Function
let pageWidth: Float = Float(self.collectionView.frame.width/3 + 20)
let currentOffset: Float = Float(scrollView.contentOffset.x)
let targetOffset: Float = Float(targetContentOffset.pointee.x)
var newTargetOffset: Float = 0
if targetOffset > currentOffset {
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth
}
else {
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth
}
if newTargetOffset < 0 {
newTargetOffset = 0
}
else if (newTargetOffset > Float(scrollView.contentSize.width)){
newTargetOffset = Float(Float(scrollView.contentSize.width))
}
targetContentOffset.pointee.x = CGFloat(currentOffset)
scrollView.setContentOffset(CGPoint(x: CGFloat(newTargetOffset), y: scrollView.contentOffset.y), animated: true)
// Make Transition Effects for cells
let duration = 0.2
var index = newTargetOffset / pageWidth;
var cell:UICollectionViewCell = self.collectionView.cellForItem(at: IndexPath(row: Int(index), section: 0))!
if (index == 0) { // If first index
UIView.animate(withDuration: duration, delay: 0.0, options: [ .curveEaseOut], animations: {
cell.transform = CGAffineTransform.identity
}, completion: nil)
index += 1
cell = self.collectionView.cellForItem(at: IndexPath(row: Int(index), section: 0))!
UIView.animate(withDuration: duration, delay: 0.0, options: [ .curveEaseOut], animations: {
cell.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}, completion: nil)
}else{
UIView.animate(withDuration: duration, delay: 0.0, options: [ .curveEaseOut], animations: {
cell.transform = CGAffineTransform.identity;
}, completion: nil)
index -= 1 // left
if let cell = self.collectionView.cellForItem(at: IndexPath(row: Int(index), section: 0)) {
UIView.animate(withDuration: duration, delay: 0.0, options: [ .curveEaseOut], animations: {
cell.transform = CGAffineTransform(scaleX: 0.8, y: 0.8);
}, completion: nil)
}
index += 1
index += 1 // right
if let cell = self.collectionView.cellForItem(at: IndexPath(row: Int(index), section: 0)) {
UIView.animate(withDuration: duration, delay: 0.0, options: [ .curveEaseOut], animations: {
cell.transform = CGAffineTransform(scaleX: 0.8, y: 0.8);
}, completion: nil)
}
}
}
@raheel-sadiq answer is great but pretty hard to understand, I think. Here's a much readable version, in my opinion:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
//minimumLineSpacing and insetForSection are two constants in my code
//this cell width is for my case, adapt to yours
let cellItemWidth = view.frame.width - (insetForSection.left + insetForSection.right)
let pageWidth = Float(cellItemWidth + minimumLineSpacing)
let offsetXAfterDragging = Float(scrollView.contentOffset.x)
let targetOffsetX = Float(targetContentOffset.pointee.x)
let pagesCountForOffset = pagesCount(forOffset: offsetXAfterDragging, withTargetOffset: targetOffsetX, pageWidth: pageWidth)
var newTargetOffsetX = pagesCountForOffset * pageWidth
keepNewTargetInBounds(&newTargetOffsetX, scrollView)
//ignore target
targetContentOffset.pointee.x = CGFloat(offsetXAfterDragging)
let newTargetPoint = CGPoint(x: CGFloat(newTargetOffsetX), y: scrollView.contentOffset.y)
scrollView.setContentOffset(newTargetPoint, animated: true)
//if you're using pageControl
pageControl.currentPage = Int(newTargetOffsetX / pageWidth)
}
fileprivate func pagesCount(forOffset offset: Float, withTargetOffset targetOffset: Float, pageWidth: Float) -> Float {
let isRightDirection = targetOffset > offset
let roundFunction = isRightDirection ? ceilf : floorf
let pagesCountForOffset = roundFunction(offset / pageWidth)
return pagesCountForOffset
}
fileprivate func keepNewTargetInBounds(_ newTargetOffsetX: inout Float, _ scrollView: UIScrollView) {
if newTargetOffsetX < 0 { newTargetOffsetX = 0 }
let contentSizeWidth = Float(scrollView.contentSize.width)
if newTargetOffsetX > contentSizeWidth { newTargetOffsetX = contentSizeWidth }
}
Edit: Demo link: https://github.com/raheelsadiq/UICollectionView-horizontal-paging-with-3-items
After a lot searching I did it, find the next point to scroll to and disable the paging. In scrollviewWillEndDragging scroll to next cell x.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
float pageWidth = 480 + 50; // width + space
float currentOffset = scrollView.contentOffset.x;
float targetOffset = targetContentOffset->x;
float newTargetOffset = 0;
if (targetOffset > currentOffset)
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth;
else
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth;
if (newTargetOffset < 0)
newTargetOffset = 0;
else if (newTargetOffset > scrollView.contentSize.width)
newTargetOffset = scrollView.contentSize.width;
targetContentOffset->x = currentOffset;
[scrollView setContentOffset:CGPointMake(newTargetOffset, scrollView.contentOffset.y) animated:YES];
}
I also had to make the left and right small and center large, so i did it with transform. The issue was finding the index, so that was very difficult to find.
For transform left and right in this same method use the newTargetOffset
int index = newTargetOffset / pageWidth;
if (index == 0) { // If first index
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[UIView animateWithDuration:ANIMATION_SPEED animations:^{
cell.transform = CGAffineTransformIdentity;
}];
cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index + 1 inSection:0]];
[UIView animateWithDuration:ANIMATION_SPEED animations:^{
cell.transform = TRANSFORM_CELL_VALUE;
}];
}else{
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[UIView animateWithDuration:ANIMATION_SPEED animations:^{
cell.transform = CGAffineTransformIdentity;
}];
index --; // left
cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[UIView animateWithDuration:ANIMATION_SPEED animations:^{
cell.transform = TRANSFORM_CELL_VALUE;
}];
index ++;
index ++; // right
cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[UIView animateWithDuration:ANIMATION_SPEED animations:^{
cell.transform = TRANSFORM_CELL_VALUE;
}];
}
And in cellForRowAtIndex add
if (indexPath.row == 0 && isfirstTimeTransform) { // make a bool and set YES initially, this check will prevent fist load transform
isfirstTimeTransform = NO;
}else{
cell.transform = TRANSFORM_CELL_VALUE; // the new cell will always be transform and without animation
}
Add these two macros too or as u wish to handle both
#define TRANSFORM_CELL_VALUE CGAffineTransformMakeScale(0.8, 0.8)
#define ANIMATION_SPEED 0.2
The end result is
Part one of @Raheel Sadiq answer in Swift 3, without Transform.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageWidth: Float = Float(self.collectionView.frame.width / 3) //480 + 50
// width + space
let currentOffset: Float = Float(scrollView.contentOffset.x)
let targetOffset: Float = Float(targetContentOffset.pointee.x)
var newTargetOffset: Float = 0
if targetOffset > currentOffset {
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth
}
else {
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth
}
if newTargetOffset < 0 {
newTargetOffset = 0
}
else if (newTargetOffset > Float(scrollView.contentSize.width)){
newTargetOffset = Float(Float(scrollView.contentSize.width))
}
targetContentOffset.pointee.x = CGFloat(currentOffset)
scrollView.setContentOffset(CGPoint(x: CGFloat(newTargetOffset), y: scrollView.contentOffset.y), animated: true)
}