UICollectionView horizontal paging with 3 items

前端 未结 6 1296
眼角桃花
眼角桃花 2020-12-04 06:21

I need to show 3 items in a UICollectionView, with paging enabled like this

\"enter

相关标签:
6条回答
  • 2020-12-04 06:37

    Mine solution to horizontal collection view paging

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if scrollView == collectionView { collectionView.scrollToPage() }
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if scrollView == collectionView { if !decelerate { collectionView.scrollToPage() } }
    }
    

    And small extension to collectionView

    public func scrollToPage() {
            var currentCellOffset = contentOffset
            currentCellOffset.x += width / 2
            var path = indexPathForItem(at: currentCellOffset)
            if path.isNil {
                currentCellOffset.x += 15
                path = indexPathForItem(at: currentCellOffset)
            }
            if path != nil {
                logInfo("Scrolling to page \(path!)")
                scrollToItem(at: path!, at: .centeredHorizontally, animated: true)
            }
        }
    
    0 讨论(0)
  • 2020-12-04 06:40

    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)
    
        }
    
    0 讨论(0)
  • 2020-12-04 06:47

    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)
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-04 06:50

    you will have to override targetContentOffsetForProposedContentOffset:withScrollingVelocity: method of the flow layout. This way you snap the stopping point of the scrollview.

    -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
    {
        CGFloat yOffset = MAXFLOAT;
    
        CGRect proposedRect;
        proposedRect.origin = proposedContentOffset;
        proposedRect.size = self.collectionView.bounds.size;
        CGPoint proposedCenterPoint = CGPointMake(CGRectGetMidX(proposedRect), CGRectGetMidY(proposedRect)) ;
    
        NSArray *array = [super layoutAttributesForElementsInRect:proposedRect];
    
        for (UICollectionViewLayoutAttributes *attributes in array)
        {
            CGFloat newOffset = attributes.center.y - proposedCenterPoint.y;
            if ( fabsf(newOffset) < fabs(yOffset))
            {
                yOffset = newOffset;
            }
        }
    
        return CGPointMake(proposedContentOffset.x, proposedContentOffset.y + yOffset);
    }
    

    Also you will beed to set the sectionInset of the flow layout to center the first cell and the last cell. My example is the height but easy to switch to width.

    CGFloat height = (self.collectionView.bounds.size.height / 2.0 ) - (self.itemSize.height / 2.0) ;
    self.sectionInset = UIEdgeInsetsMake(height, 30.0, height, 30.0) ;
    
    0 讨论(0)
  • 2020-12-04 06:57

    @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 }
        }
    
    0 讨论(0)
  • 2020-12-04 07:03

    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

    enter image description here

    0 讨论(0)
提交回复
热议问题