启用分页时将水平可滚动集合视图的单元格居中并使其他单元格部分可见

Posted

技术标签:

【中文标题】启用分页时将水平可滚动集合视图的单元格居中并使其他单元格部分可见【英文标题】:Centring a cell of a horizontally scrollable collection view when paging is enabled and making other cells partly visible, too 【发布时间】:2018-03-23 14:59:18 【问题描述】:

我有一个可水平滚动的集合视图。它上面有几个细胞。为该集合视图启用了分页。

我想让它显示居中单元格左右两侧的部分单元格(当它不是第一个或最后一个时),如下所示:

这里是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

sectionInsets 如下:

 private var sectionInsets = UIEdgeInsetsMake(8, 20, 8, 20)

目前,当我向右滚动时,右侧单元格变得越来越明显(单元格之间的间距线向左边缘移动)

我尝试根据here和here的答案使用不同的值,但无法达到我想要的效果。

如果您对此有任何建议,我将不胜感激。

【问题讨论】:

【参考方案1】:

实际上,解决方案并不难。感谢this 文章显示了实现此功能的正确方法,我终于实现了它。

要了解的主要内容是,在这种情况下,内置在集合视图中的分页将不起作用,因为单元格的大小小于屏幕的大小。所以,我们需要实现我们的自定义分页技术。

首先,在自定义助手struct 中,我们定义了一个属性,该属性将在拖动之前保存单元格的索引:

var indexOfCellBeforeDragging: Int = 0

然后,我们创建一个方法来计算单元格的section inset:

 //Setting the inset
func calculateSectionInset(forCollectionViewLayout collectionViewLayout: UICollectionViewFlowLayout, numberOfCells: Int) -> CGFloat 
    let inset = (collectionViewLayout.collectionView!.frame.width) / CGFloat(numberOfCells)
    return inset

现在我们可以使用上面的方法来设置collection view的items的大小了。

  //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()

我们需要获取主单元格(现在最大的单元格)的索引。为此,我们在x 轴上使用有关项目宽度和集合视图的contentOffset 的信息:

//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))

接下来,我们在开始拖动之前设置单元格的索引,使用上面的方法:

 //Setting the index of cell before starting dragging
mutating func setIndexOfCellBeforeStartingDragging(indexOfMajorCell: Int) 
    indexOfCellBeforeDragging = indexOfMajorCell

现在我们应该处理拖动的结束。此外,我们会处理单元格上可能的捕捉手势。这里我们为滑动速度使用了一些自定义阈值。

  //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)
    

现在,在 scrollViewWillBeginDragging(_:) 方法内的视图控制器中,我们需要获取主单元格的索引并将其设置为开始拖动之前的索引(commentsPagingHelper 是辅助结构的一个实例):

  func scrollViewWillBeginDragging(_ scrollView: UIScrollView) 

    let indexOfMajorCell = commentsPagingHelper.indexOfMajorCell(in: collectionViewLayout)
    commentsPagingHelper.setIndexOfCellBeforeStartingDragging(indexOfMajorCell: indexOfMajorCell)

最后,我们在scrollViewWillEndDragging(_:, withVelocity:, targetContentOffset:)方法中处理主单元格索引的变化:

 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)

就是这样。希望,这将帮助有类似问题的人。

【讨论】:

【参考方案2】:

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;


在您的情况下,collectionViewCellwidth 将为“collectionView.frame.width - 50”

【讨论】:

以上是关于启用分页时将水平可滚动集合视图的单元格居中并使其他单元格部分可见的主要内容,如果未能解决你的问题,请参考以下文章

具有动态高度的集合视图水平方向

UICollectionView 在启用分页的情况下不会滚动完整的 320 点

启用collectionview分页时将collection view滚动到索引

具有自我调整大小的单元格的集合视图不再可水平滚动

集合视图单元格快速滚动到中心时出现问题

SWIFT:如何修复单元格荧光笔并使整个集合视图滚动而不是荧光笔?