水平滚动UICollectionView时,捕捉到单元格的中心


69

我知道有些人以前曾问过这个问题,但是他们全都UITableViews或者或者UIScrollViews我无法获得公认的解决方案来为我工作。我想要的是UICollectionView水平滚动时的捕捉效果-就像iOS AppStore中发生的情况一样。iOS 9+是我的目标版本,因此在回答此问题之前,请先查看UIKit的更改。

谢谢。

Answers:


118

当我最初使用Objective-C时,自那以后我切换到了Swift,最初接受的答案就不够了。

我最终创建了一个UICollectionViewLayout子类,该子类提供了最佳的(imo)体验,而其他功能则在用户停止滚动时更改内容偏移量或类似功能。

class SnappingCollectionViewLayout: UICollectionViewFlowLayout {

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }

        var offsetAdjustment = CGFloat.greatestFiniteMagnitude
        let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left

        let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)

        let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)

        layoutAttributesArray?.forEach({ (layoutAttributes) in
            let itemOffset = layoutAttributes.frame.origin.x
            if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) {
                offsetAdjustment = itemOffset - horizontalOffset
            }
        })

        return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
    }
}

对于当前布局子类,最自然的感觉减速,请确保设置以下内容:

collectionView?.decelerationRate = UIScrollViewDecelerationRateFast


3
由于某种原因,这导致我的收藏夹视图垂直,并调整了我所有单元的大小,有何见解?
Jerland2

1
最后一行是一个很好的提示!从未听说过此属性!
Lucien

4
对于已sectionInset.left设定条件的人,将退货声明替换为:return CGPoint(x: proposedContentOffset.x + offsetAdjustment - sectionInset.left, y: proposedContentOffset.y)
Andrey Gordeev

2
这种解决方案虽然可以捕捉单元格,但由于不尊重速度,因此它的动画非常容易出错。如果用户快速滑动,您会发现它经常出现毛刺。
NSPunk

2
我需要在滑动时始终捕捉到下一个单元格,因此我通过检查滑动方向对其进行了一些调整:layoutAttributesArray?.forEach({ (layoutAttributes) in let itemOffset = layoutAttributes.frame.origin.x let itemWidth = Float(layoutAttributes.frame.width) let direction: Float = velocity.x > 0 ? 1 : -1 if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) + itemWidth * direction { offsetAdjustment = itemOffset - horizontalOffset } })
Leah Culver

31

根据Mete的回答和Chris Chute的评论

这是一个Swift 4扩展,可以完成OP想要的操作。它已经在单行和双行嵌套集合视图上进行了测试,并且效果很好。

extension UICollectionView {
    func scrollToNearestVisibleCollectionViewCell() {
        self.decelerationRate = UIScrollViewDecelerationRateFast
        let visibleCenterPositionOfScrollView = Float(self.contentOffset.x + (self.bounds.size.width / 2))
        var closestCellIndex = -1
        var closestDistance: Float = .greatestFiniteMagnitude
        for i in 0..<self.visibleCells.count {
            let cell = self.visibleCells[i]
            let cellWidth = cell.bounds.size.width
            let cellCenter = Float(cell.frame.origin.x + cellWidth / 2)

            // Now calculate closest cell
            let distance: Float = fabsf(visibleCenterPositionOfScrollView - cellCenter)
            if distance < closestDistance {
                closestDistance = distance
                closestCellIndex = self.indexPath(for: cell)!.row
            }
        }
        if closestCellIndex != -1 {
            self.scrollToItem(at: IndexPath(row: closestCellIndex, section: 0), at: .centeredHorizontally, animated: true)
        }
    }
}

您需要UIScrollViewDelegate为您的集合视图实现协议,然后添加以下两种方法:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    self.collectionView.scrollToNearestVisibleCollectionViewCell()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        self.collectionView.scrollToNearestVisibleCollectionViewCell()
    }
}

这个答案最适合我。很好,很顺利。我确实做了更改,因为我有单元间距并且不希望它居中。还希望控制动画的持续时间,因此:if closestCellIndex != -1 { UIView.animate(withDuration: 0.1) { let toX = (cellWidth + cellHorizontalSpacing) * CGFloat(closestCellIndex) scrollView.contentOffset = CGPoint(x: toX, y: 0) scrollView.layoutIfNeeded() } }
Edan

@ vahid-amiri很棒。谢谢。在世界上是怎么学到的呢!对我来说还有很长的路要走:)
ashishn

它适用于垂直滚动吗?水平滚动没有问题
karthikeyan

尽管您将要Paging Enabled在要为其实现此扩展的collectionView上禁用,但该解决方案也对我有用。启用分页后,行为是不可预测的。我相信这是因为自动分页功能会干扰手动计算。
B拉德

26

遵循滚动速度,捕捉到最近的单元格。

正常工作。

import UIKit

class SnapCenterLayout: UICollectionViewFlowLayout {
  override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
    guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }
    let parent = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)

    let itemSpace = itemSize.width + minimumInteritemSpacing
    var currentItemIdx = round(collectionView.contentOffset.x / itemSpace)

    // Skip to the next cell, if there is residual scrolling velocity left.
    // This helps to prevent glitches
    let vX = velocity.x
    if vX > 0 {
      currentItemIdx += 1
    } else if vX < 0 {
      currentItemIdx -= 1
    }

    let nearestPageOffset = currentItemIdx * itemSpace
    return CGPoint(x: nearestPageOffset,
                   y: parent.y)
  }
}

1
到目前为止,这是最好的方法。即使很小的触摸,它也尊重速度。对于使用的项目contentInset,请确保使用var添加删除nearestPageOffset
NSPunk '18

@NSPunk同意...很多流行的应用程序都采用类似的设计,并且当仍然有速度,但不足以捕捉到下一个单元时,它们都有奇怪的毛刺。
理查德·托普奇

我必须将minimumLineSpacing添加到itemSpace以使其正确。干得好!
Nico S.

这不能正确反映itemSize。此方法只能itemSize直接设置为layout,但是如果itemSize通过设置UICollectionViewDelegateFlowLayout,则此新设置的值将无法识别
gondo

25

对于这里值得的是,我使用了一个简单的计算方法(迅速):

func snapToNearestCell(_ collectionView: UICollectionView) {
    for i in 0..<collectionView.numberOfItems(inSection: 0) {

        let itemWithSpaceWidth = collectionViewFlowLayout.itemSize.width + collectionViewFlowLayout.minimumLineSpacing
        let itemWidth = collectionViewFlowLayout.itemSize.width

        if collectionView.contentOffset.x <= CGFloat(i) * itemWithSpaceWidth + itemWidth / 2 {                
            let indexPath = IndexPath(item: i, section: 0)
            collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
            break
        }
    }
}

致电您需要的地方。我叫它

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    snapToNearestCell(scrollView)
}

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    snapToNearestCell(scrollView)
}

collectionViewFlowLayout可能来自哪里:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // Set up collection view
    collectionViewFlowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
}

1
谢谢!这是最干净的,最简单的答案在我看来
大卫Ç

没问题。在计算多个单元格时,我实际上遇到了一些问题,因为计算错误。因此更新了我的答案。
Nam

集合
Ethan SK

该解决方案可以工作,但感觉不是本地的,因为它没有在完全减速之前就开始捕捉。
fl034

19

SWIFT 3版本的@ Iowa15答复

func scrollToNearestVisibleCollectionViewCell() {
    let visibleCenterPositionOfScrollView = Float(collectionView.contentOffset.x + (self.collectionView!.bounds.size.width / 2))
    var closestCellIndex = -1
    var closestDistance: Float = .greatestFiniteMagnitude
    for i in 0..<collectionView.visibleCells.count {
        let cell = collectionView.visibleCells[i]
        let cellWidth = cell.bounds.size.width
        let cellCenter = Float(cell.frame.origin.x + cellWidth / 2)

        // Now calculate closest cell
        let distance: Float = fabsf(visibleCenterPositionOfScrollView - cellCenter)
        if distance < closestDistance {
            closestDistance = distance
            closestCellIndex = collectionView.indexPath(for: cell)!.row
        }
    }
    if closestCellIndex != -1 {
        self.collectionView!.scrollToItem(at: IndexPath(row: closestCellIndex, section: 0), at: .centeredHorizontally, animated: true)
    }
}

需要在UIScrollViewDelegate中实现:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    scrollToNearestVisibleCollectionViewCell()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        scrollToNearestVisibleCollectionViewCell()
    }
}

2
这对我来说很棒。我要补充一点,如果您collectionView.decelerationRate = UIScrollViewDecelerationRateFast在某个时候进行设置,则您将更接近App Store的“感觉” 。我还要补充一句,FLT_MAX将第4行更改为Float.greatestFiniteMagnitude,以避免Xcode警告。
克里斯·楚特

13

这是我的实现

func snapToNearestCell(scrollView: UIScrollView) {
     let middlePoint = Int(scrollView.contentOffset.x + UIScreen.main.bounds.width / 2)
     if let indexPath = self.cvCollectionView.indexPathForItem(at: CGPoint(x: middlePoint, y: 0)) {
          self.cvCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
     }
}

像这样实现滚动视图委托

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    self.snapToNearestCell(scrollView: scrollView)
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    self.snapToNearestCell(scrollView: scrollView)
}

另外,为了更好地捕捉

self.cvCollectionView.decelerationRate = UIScrollViewDecelerationRateFast

奇迹般有效


1
您忘记将第一个代码段放在一个名为“ snapToNearestCell(scrollView:UIScrollView)”的函数中
Starsky,

12

如果您想要简单的本机行为,而无需自定义:

collectionView.pagingEnabled = YES;

仅当集合视图布局项目的大小全部为一种大小且UICollectionViewCellclipToBounds属性设置为时,此方法才能正常工作YES


这不能提供最好的反馈,也不能与具有多行的集合视图一起正常工作,但是确实很容易做到。
Vahid Amiri '18

如果您使用了此设置,但您的单元格未居中,请确保将minimumLineSpacing布局设置为0
M Reza

10

我同时尝试了@Mark Bourke和@mrcrowley解决方案,但它们给出的结果几乎相同,并且具有不良的粘滞效果。

我设法通过考虑解决问题velocity。这是完整的代码。

final class BetterSnappingLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
    guard let collectionView = collectionView else {
        return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
    }

    var offsetAdjusment = CGFloat.greatestFiniteMagnitude
    let horizontalCenter = proposedContentOffset.x + (collectionView.bounds.width / 2)

    let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
    let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
    layoutAttributesArray?.forEach({ (layoutAttributes) in
        let itemHorizontalCenter = layoutAttributes.center.x

        if abs(itemHorizontalCenter - horizontalCenter) < abs(offsetAdjusment) {
            if abs(velocity.x) < 0.3 { // minimum velocityX to trigger the snapping effect
                offsetAdjusment = itemHorizontalCenter - horizontalCenter
            } else if velocity.x > 0 {
                offsetAdjusment = itemHorizontalCenter - horizontalCenter + layoutAttributes.bounds.width
            } else { // velocity.x < 0
                offsetAdjusment = itemHorizontalCenter - horizontalCenter - layoutAttributes.bounds.width
            }
        }
    })

    return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y)
}

}


当将collectionView.decelerationRate = UIScrollView.DecelerationRate.fast添加到collectionView本身时,这对我最有效
Lance Samaria

1
我登录只是为了支持这一点,然后说:这是迄今为止该页面上最好的解决方案,并且立即按照我的期望进行了工作。我花了太长时间来调整最受好评的榜样。谢谢Nhon!
安德鲁·科诺夫

是的,这似乎可以做到。
温斯顿·杜

4

得到了一个SO的答案在这里这里的文档

首先,您可以做的是通过将类设为滚动视图委托来设置集合视图的scrollview委托类

MyViewController : SuperViewController<... ,UIScrollViewDelegate>

然后将您的视图控制器设置为委托

UIScrollView *scrollView = (UIScrollView *)super.self.collectionView;
scrollView.delegate = self;

或在界面构建器中通过以下方式进行操作:Ctrl + Shift单击集合视图,然后按住Control拖动或右键单击拖动到视图控制器并选择委托。(您应该知道如何执行此操作)。那不行 UICollectionView是UIScrollView的子类,因此您现在可以通过按Control + Shift单击来在界面生成器中看到它

接下来实现委托方法 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

MyViewController.m

... 

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{

}

该文档指出:

参量

scrollView | 滚动视图对象正在使内容视图的滚动减速。

讨论当滚动运动停止时,滚动视图将调用此方法。UIScrollView的decelerating属性控制减速度。

可用性在iOS 2.0和更高版本中可用。

然后在该方法内部检查停止滚动时哪个单元格最靠近scrollview的中心

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  //NSLog(@"%f", truncf(scrollView.contentOffset.x + (self.pictureCollectionView.bounds.size.width / 2)));

float visibleCenterPositionOfScrollView = scrollView.contentOffset.x + (self.pictureCollectionView.bounds.size.width / 2);

//NSLog(@"%f", truncf(visibleCenterPositionOfScrollView / imageArray.count));


NSInteger closestCellIndex;

for (id item in imageArray) {
    // equation to use to figure out closest cell
    // abs(visibleCenter - cellCenterX) <= (cellWidth + cellSpacing/2)

    // Get cell width (and cell too)
    UICollectionViewCell *cell = (UICollectionViewCell *)[self collectionView:self.pictureCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathWithIndex:[imageArray indexOfObject:item]]];
    float cellWidth = cell.bounds.size.width;

    float cellCenter = cell.frame.origin.x + cellWidth / 2;

    float cellSpacing = [self collectionView:self.pictureCollectionView layout:self.pictureCollectionView.collectionViewLayout minimumInteritemSpacingForSectionAtIndex:[imageArray indexOfObject:item]];

    // Now calculate closest cell

    if (fabsf(visibleCenterPositionOfScrollView - cellCenter) <= (cellWidth + (cellSpacing / 2))) {
        closestCellIndex = [imageArray indexOfObject:item];
        break;
    }
}

if (closestCellIndex != nil) {

[self.pictureCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathWithIndex:closestCellIndex] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];

// This code is untested. Might not work.

}

我现在正在计算粗略的计算内容,大约需要10分钟才能进行基本的计算。
Minebomber

如果您正在使用多个集合视图,并且已将viewController设置为其委托,则可以简单地针对collectionView检查scrollView,因为collectionView继承自scrollView。这是为了找出哪个collectionView已停止滚动。
纽约市技术工程师

@Minebomber嘿,伙计,刚刚尝试了一下,当我尝试在for循环中获取cellForItemAtIndexPath时,我一直遇到访问错误。令我惊讶的是,由于我认为使用collectionView总是可以确保您拥有一个单元格,因此引发了一个错误。
纽约市技术工程师

是的,因此在实际的委托中显式调用委托回调是一个坏主意。
纽约市技术工程师

因此,我最终遍历了collectionView.visibleCells来解决我的问题。我仍然最终还是手动调用了委托方法来选择单元格……
纽约市技术工程师

2

您可以尝试对上述答案进行修改:

-(void)scrollToNearestVisibleCollectionViewCell {
    float visibleCenterPositionOfScrollView = _collectionView.contentOffset.x + (self.collectionView.bounds.size.width / 2);

    NSInteger closestCellIndex = -1;
    float closestDistance = FLT_MAX;
    for (int i = 0; i < _collectionView.visibleCells.count; i++) {
        UICollectionViewCell *cell = _collectionView.visibleCells[i];
        float cellWidth = cell.bounds.size.width;

        float cellCenter = cell.frame.origin.x + cellWidth / 2;

        // Now calculate closest cell
        float distance = fabsf(visibleCenterPositionOfScrollView - cellCenter);
        if (distance < closestDistance) {
            closestDistance = distance;
            closestCellIndex = [_collectionView indexPathForCell:cell].row;
        }
    }

    if (closestCellIndex != -1) {
        [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:closestCellIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
    }
}

2

我刚刚发现我认为是解决此问题的最佳方法:

首先,将一个目标添加到collectionView的已经存在的actionRecognizer中:

[self.collectionView.panGestureRecognizer addTarget:self action:@selector(onPan:)];

让选择器指向以UIPanGestureRecognizer作为参数的方法:

- (void)onPan:(UIPanGestureRecognizer *)recognizer {};

然后在此方法中,当平移手势结束时,强制collectionView滚动到适当的单元格。我通过从收藏夹视图中看到可见的项目并根据平移方向确定要滚动到哪个项目来做到这一点。

if (recognizer.state == UIGestureRecognizerStateEnded) {

        // Get the visible items
        NSArray<NSIndexPath *> *indexes = [self.collectionView indexPathsForVisibleItems];
        int index = 0;

        if ([(UIPanGestureRecognizer *)recognizer velocityInView:self.view].x > 0) {
            // Return the smallest index if the user is swiping right
            for (int i = index;i < indexes.count;i++) {
                if (indexes[i].row < indexes[index].row) {
                    index = i;
                }
            }
        } else {
            // Return the biggest index if the user is swiping left
            for (int i = index;i < indexes.count;i++) {
                if (indexes[i].row > indexes[index].row) {
                    index = i;
                }
            }
        }
        // Scroll to the selected item
        [self.collectionView scrollToItemAtIndexPath:indexes[index] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
    }

请记住,就我而言,一次只能看到两个项目。我敢肯定,这种方法可以适应更多的项目。


2

摘自2012年WWDC视频中的Objective-C解决方案。我将UICollectionViewFlowLayout子类化,并添加了以下内容。

-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
    {
        CGFloat offsetAdjustment = MAXFLOAT;
        CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2);

        CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
        NSArray *array = [super layoutAttributesForElementsInRect:targetRect];

        for (UICollectionViewLayoutAttributes *layoutAttributes in array)
        {
            CGFloat itemHorizontalCenter = layoutAttributes.center.x;
            if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment))
            {
                offsetAdjustment = itemHorizontalCenter - horizontalCenter;
            }
        }

        return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
    }

我之所以要问这个问题,是因为它具有本机感,这是我从Mark的可接受答案中得到的……这是我放入collectionView的视图控制器中的结果。

collectionView.decelerationRate = UIScrollViewDecelerationRateFast;

2

该解决方案提供了更好,更流畅的动画。

迅捷3

要使第一个和最后一个项目居中,请添加插图:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

    return UIEdgeInsetsMake(0, cellWidth/2, 0, cellWidth/2)
}

然后使用targetContentOffset中的scrollViewWillEndDragging方法更改结束位置。

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    let numOfItems = collectionView(mainCollectionView, numberOfItemsInSection:0)
    let totalContentWidth = scrollView.contentSize.width + mainCollectionViewFlowLayout.minimumInteritemSpacing - cellWidth
    let stopOver = totalContentWidth / CGFloat(numOfItems)

    var targetX = round((scrollView.contentOffset.x + (velocity.x * 300)) / stopOver) * stopOver
    targetX = max(0, min(targetX, scrollView.contentSize.width - scrollView.frame.width))

    targetContentOffset.pointee.x = targetX
}

也许在您的情况下,totalContentWidth计算的是不一样的,fe没有minimumInteritemSpacing,因此请相应地调整。您也可以玩300velocity

PS确保课程采用UICollectionViewDataSource协议


嘿罗兰,生活如何?只是遇到了您对这个问题的回答...很老了,但仍然很好,谢谢!仅供参考,collectionView(mainCollectionView, numberOfItemsInSection:0)如果您的对象采用,则只能以这种方式使用该方法UICollectionViewDataSource。为什么cellWidth要从中减去scrollView.contentSize.width,总宽度不总是总是scrollView.contentSize.width吗?
Paul van Roosendaal

嗨,保罗!在我的情况下,cellWidth需要减去来补偿它以使其居中。也许在您的情况下,totalContentWidth计算方式有所不同。
罗兰·基瑟姆

1

这是一个Swift 3.0版本,根据Mark的上述建议,该版本应同时适用于水平和垂直方向:

  override func targetContentOffset(
    forProposedContentOffset proposedContentOffset: CGPoint,
    withScrollingVelocity velocity: CGPoint
  ) -> CGPoint {

    guard
      let collectionView = collectionView
    else {
      return super.targetContentOffset(
        forProposedContentOffset: proposedContentOffset,
        withScrollingVelocity: velocity
      )
    }

    let realOffset = CGPoint(
      x: proposedContentOffset.x + collectionView.contentInset.left,
      y: proposedContentOffset.y + collectionView.contentInset.top
    )

    let targetRect = CGRect(origin: proposedContentOffset, size: collectionView.bounds.size)

    var offset = (scrollDirection == .horizontal)
      ? CGPoint(x: CGFloat.greatestFiniteMagnitude, y:0.0)
      : CGPoint(x:0.0, y:CGFloat.greatestFiniteMagnitude)

    offset = self.layoutAttributesForElements(in: targetRect)?.reduce(offset) {
      (offset, attr) in
      let itemOffset = attr.frame.origin
      return CGPoint(
        x: abs(itemOffset.x - realOffset.x) < abs(offset.x) ? itemOffset.x - realOffset.x : offset.x,
        y: abs(itemOffset.y - realOffset.y) < abs(offset.y) ? itemOffset.y - realOffset.y : offset.y
      )
    } ?? .zero

    return CGPoint(x: proposedContentOffset.x + offset.x, y: proposedContentOffset.y + offset.y)
  }

知道为什么这会更改我的收藏视图单元格大小并使我的收藏视图设置为水平,垂直吗?
Jerland2

1

斯威夫特4.2。简单。对于固定的itemSize。水平流向。

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
        let floatingPage = targetContentOffset.pointee.x/scrollView.bounds.width
        let rule: FloatingPointRoundingRule = velocity.x > 0 ? .up : .down
        let page = CGFloat(Int(floatingPage.rounded(rule)))
        targetContentOffset.pointee.x = page*(layout.itemSize.width + layout.minimumLineSpacing)
    }

}

不幸的是,jumping to previous cell当您向左滚动并立即单击时(滚动动画仍在发生时)。您将看到滚动被“取消”并跳到上一个单元格。
gondo

1

我一直通过在uicollectionview的属性检查器上设置“ Paging Enabled”来解决此问题。

对我来说,这发生在单元格的宽度与uicollectionview的宽度相同时。

不涉及编码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.