Reputation: 1451
I have a horizontally scrollable collection view. It has several cells on it. Paging is enabled for that collection view.
I would like to make it show part of cells from left and right of the centred cell (when it's not the first or the last one) like here:
Here is the code of UICollectionViewDelegateFlowLayout
:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width - 50, height: collectionView.frame.height - 16)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
And sectionInsets
are the following:
private var sectionInsets = UIEdgeInsetsMake(8, 20, 8, 20)
Currently, when I scroll to the right, the right cell gets more and more visible (the spacing line between cells move towards the left edge)
I've tried to use different values according to answers here and here but I couldn't achieve the effect I want.
If you have suggestions about this, I would appreciate your help.
Upvotes: 2
Views: 2323
Reputation: 1451
Actually, the solution isn't very hard. Thanks to this article that shows the right way to implement this feature, I finally implemented it.
The main thing to understand is that the paging, built in a collection view, won't work in this case, because the size of cells is less than the size of the screen. So, we need to implement our custom paging technology.
First, inside of a custom helper struct
, we define a property that will hold the index of the cell before dragging :
var indexOfCellBeforeDragging: Int = 0
Then, we create a method for calculating the section inset of cells:
//Setting the inset
func calculateSectionInset(forCollectionViewLayout collectionViewLayout: UICollectionViewFlowLayout, numberOfCells: Int) -> CGFloat {
let inset = (collectionViewLayout.collectionView!.frame.width) / CGFloat(numberOfCells)
return inset
}
Now we can use the method above, to set the size of items of the collection view.
//Setting the item size of the collection view
func configureCollectionViewLayoutItemSize(forCollectionViewLayout collectionViewLayout: UICollectionViewFlowLayout) {
let inset: CGFloat = calculateSectionInset(forCollectionViewLayout: collectionViewLayout, numberOfCells: 5)
collectionViewLayout.sectionInset = UIEdgeInsets(top: 0, left: inset/4, bottom: 0, right: inset/4)
collectionViewLayout.itemSize = CGSize(width: collectionViewLayout.collectionView!.frame.size.width - inset / 2, height: collectionViewLayout.collectionView!.frame.size.height)
collectionViewLayout.collectionView?.reloadData()
}
We need to get the index of the major cell (the cell that is in the biggest right now). To do so, we use the information about items width and collection view's contentOffset
on the x
axis:
//Getting the index of the major cell
func indexOfMajorCell(in collectionViewLayout: UICollectionViewFlowLayout) -> Int {
let itemWidth = collectionViewLayout.itemSize.width
let proportionalLayout = collectionViewLayout.collectionView!.contentOffset.x / itemWidth
return Int(round(proportionalLayout))
}
Next, we set the index of cell before starting dragging, using the method above:
//Setting the index of cell before starting dragging
mutating func setIndexOfCellBeforeStartingDragging(indexOfMajorCell: Int) {
indexOfCellBeforeDragging = indexOfMajorCell
}
Now we should handle the end of the dragging. Also, we take care of the possible snapping gesture on cells. Here we use some custom threshold for the swipe velocity.
//Handling dragging end of a scroll view
func handleDraggingWillEndForScrollView(_ scrollView: UIScrollView, inside collectionViewLayout: UICollectionViewFlowLayout, withVelocity velocity: CGPoint, usingIndexOfMajorCell indexOfMajorCell: Int) {
//Calculating where scroll view should snap
let indexOfMajorCell = indexOfMajorCell
let swipeVelocityThreshold: CGFloat = 0.5
let majorCellIsTheCellBeforeDragging = indexOfMajorCell == indexOfCellBeforeDragging
let hasEnoughVelocityToSlideToTheNextCell = indexOfCellBeforeDragging + 1 < 5 && velocity.x > swipeVelocityThreshold
let hasEnoughVelocityToSlideToThePreviousCell = ((indexOfCellBeforeDragging - 1) >= 0) && (velocity.x < -swipeVelocityThreshold)
let didUseSwipeToSkipCell = majorCellIsTheCellBeforeDragging && (hasEnoughVelocityToSlideToTheNextCell || hasEnoughVelocityToSlideToThePreviousCell)
if didUseSwipeToSkipCell {
let snapToIndex = indexOfCellBeforeDragging + (hasEnoughVelocityToSlideToTheNextCell ? 1 : -1)
let toValue = collectionViewLayout.itemSize.width * CGFloat(snapToIndex)
// Damping equal 1 => no oscillations => decay animation
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: velocity.x, options: .allowUserInteraction, animations: {
scrollView.contentOffset = CGPoint(x: toValue, y: 0)
scrollView.layoutIfNeeded()
}, completion: nil)
} else {
let indexPath = IndexPath(row: indexOfMajorCell, section: 0)
collectionViewLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
Now, in your view controller inside scrollViewWillBeginDragging(_:)
method we need to get the index of the major cell and set it as an index before starting dragging (commentsPagingHelper
is an instance of the helper struct):
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let indexOfMajorCell = commentsPagingHelper.indexOfMajorCell(in: collectionViewLayout)
commentsPagingHelper.setIndexOfCellBeforeStartingDragging(indexOfMajorCell: indexOfMajorCell)
}
Finally, we handle the change of an index of the major cell inside scrollViewWillEndDragging(_:, withVelocity:, targetContentOffset:)
method:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
//Stop scrollView sliding:
targetContentOffset.pointee = scrollView.contentOffset
let indexOfMajorCell = commentsPagingHelper.indexOfMajorCell(in: collectionViewLayout)
commentsPagingHelper.handleDraggingWillEndForScrollView(scrollView, inside: collectionViewLayout, withVelocity: velocity, usingIndexOfMajorCell: indexOfMajorCell)
}
That's it. Hope, this will help someone with similar issue.
Upvotes: 2
Reputation: 61
NSInteger lastOffset, currentIndex;
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
lastOffset = scrollView.contentOffset.x;
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
CGFloat newOffset = scrollView.contentOffset.x;
if (currentIndex > 0 && lastOffset > 0 && (newOffset < lastOffset - (collectionViewCellwidth / 2.0) || velocity.x < -0.5)) {
currentIndex -= 1;
}
if (currentIndex < (totalNumberOfRows - 1) && lastOffset < ((totalNumberOfRows - 1) * collectionViewCellwidth) && (newOffset > (lastOffset + (collectionViewCellwidth / 2.0)) || velocity.x > 0.5)) {
currentIndex += 1 ;
}
targetContentOffset->x = currentIndex * collectionViewCellwidth;
}
In your case collectionViewCellwidth will be "collectionView.frame.width - 50"
Upvotes: 0