UICollectionView与分页 – 设置页面宽度

我有一个集合视图,一次可以显示大约3.5个单元格,我希望它可以分页启用。 但我希望它捕捉到每个单元格(就像App Store应用程序一样),而不是滚动视图的全部宽度。 我怎样才能做到这一点?

您可以通过作为集合视图的委托并实现该方法来捕捉到单元格:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 

这告诉你,用户已经完成了一个拖动,它允许你修改targetContentOffset来alignment你的单元格(即到最近的单元格)。 请注意,您需要小心如何修改targetContentOffset; 特别是,你需要避免改变它,使视图需要在传递的速度的相反方向滚动,否则你会得到animation故障。 如果你是谷歌的方法名称,你可能会find很多这样的例子。

另一种方法是创build一个自定义的UICollectionViewFlowLayout,并重写像这样的方法:

 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)offset withScrollingVelocity:(CGPoint)velocity { CGRect cvBounds = self.collectionView.bounds; CGFloat halfWidth = cvBounds.size.width * 0.5f; CGFloat proposedContentOffsetCenterX = offset.x + halfWidth; NSArray* attributesArray = [self layoutAttributesForElementsInRect:cvBounds]; UICollectionViewLayoutAttributes* candidateAttributes; for (UICollectionViewLayoutAttributes* attributes in attributesArray) { // == Skip comparison with non-cell items (headers and footers) == // if (attributes.representedElementCategory != UICollectionElementCategoryCell) { continue; } // == First time in the loop == // if(!candidateAttributes) { candidateAttributes = attributes; continue; } if (fabsf(attributes.center.x - proposedContentOffsetCenterX) < fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) { candidateAttributes = attributes; } } return CGPointMake(candidateAttributes.center.x - halfWidth, offset.y); } 

https://gist.github.com/mmick66/9812223

  • 注意:当我们真正显示预览单元格时(即使它们具有0.0f的alpha值),这将起作用。 这是因为如果预览单元格在滚动属性时不可用,对象将不会在循环中传递…

如果您正在寻找Swift解决scheme,我还创build了一个包含代码的小教程 。

在查看这里的内容之前,我开发了我的解决scheme。 我也去创build一个自定义的UICollectionViewFlowLayout并重写targetContentOffset方法。

它似乎对我来说工作正常(即我得到了与AppStore相同的行为),即使我得到更less的代码。 在这里,请随时指出我可以想到的任何缺点:

 override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { let inset: Int = 10 let vcBounds = self.collectionView!.bounds var candidateContentOffsetX: CGFloat = proposedContentOffset.x for attributes in self.layoutAttributesForElements(in: vcBounds)! as [UICollectionViewLayoutAttributes] { if vcBounds.origin.x < attributes.center.x { candidateContentOffsetX = attributes.frame.origin.x - CGFloat(inset) break } } return CGPoint(x: candidateContentOffsetX, y: proposedContentOffset.y) } 

解决scheme迈克先生提出的职位之前,我工作,但在我的情况下,我想有第一个单元格开始在collectionView的中间。 所以我使用集合stream委托方法来定义一个插入( collectionView:layout:insetForSectionAtIndex: 。 这使得第一个单元格和第二个单元格之间的滚动被卡住,不能正确滚动到第一个单元格。

这是因为candidateAttributes.center.x - halfWidth具有负值。 解决的办法是获得绝对值,所以我添加晶圆到这​​一行return CGPointMake(fabs(candidateAttributes.center.x - halfWidth), offset.y);

Fabs应该默认添加以覆盖所有情况。

这是我的解决scheme。 适用于任何页面宽度。

设置self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast感觉真正的分页。

解决scheme是基于一个部分滚动按项分页。

 - (CGFloat)pageWidth { return self.itemSize.width + self.minimumLineSpacing; } - (CGPoint)offsetAtCurrentPage { CGFloat width = -self.collectionView.contentInset.left - self.sectionInset.left; for (int i = 0; i < self.currentPage; i++) width += [self pageWidth]; return CGPointMake(width, 0); } - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset { return [self offsetAtCurrentPage]; } - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { // To scroll paginated /* if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfItemsInSection:0]-1) self.currentPage += 1; else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1; return [self offsetAtCurrentPage]; */ // To scroll and stop always at the center of a page CGRect proposedRect = CGRectMake(proposedContentOffset.x+self.collectionView.bounds.size.width/2 - self.pageWidth/2, 0, self.pageWidth, self.collectionView.bounds.size.height); NSMutableArray <__kindof UICollectionViewLayoutAttributes *> *allAttributes = [[self layoutAttributesForElementsInRect:proposedRect] mutableCopy]; __block UICollectionViewLayoutAttributes *proposedAttributes = nil; __block CGFloat minDistance = CGFLOAT_MAX; [allAttributes enumerateObjectsUsingBlock:^(__kindof UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { CGFloat distance = CGRectGetMidX(proposedRect) - obj.center.x; if (ABS(distance) < minDistance) { proposedAttributes = obj; minDistance = distance; } }]; // Scroll always if (self.currentPage == proposedAttributes.indexPath.row) { if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfItemsInSection:0]-1) self.currentPage += 1; else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1; } else { self.currentPage = proposedAttributes.indexPath.row; } return [self offsetAtCurrentPage]; } 

这是分段分页。

 - (CGPoint)offsetAtCurrentPage { CGFloat width = -self.collectionView.contentInset.leff; for (int i = 0; i < self.currentPage; i++) width += [self sectionWidth:i]; return CGPointMake(width, 0); } - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset { return [self offsetAtCurrentPage]; } - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { // To scroll paginated /* if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfSections]-1) self.currentPage += 1; else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1; return [self offsetAtCurrentPage]; */ // To scroll and stop always at the center of a page CGRect proposedRect = CGRectMake(proposedContentOffset.x+self.collectionView.bounds.size.width/2 - [self sectionWidth:0]/2, 0, [self sectionWidth:0], self.collectionView.bounds.size.height); NSMutableArray <__kindof UICollectionViewLayoutAttributes *> *allAttributes = [[self layoutAttributesForElementsInRect:proposedRect] mutableCopy]; __block UICollectionViewLayoutAttributes *proposedAttributes = nil; __block CGFloat minDistance = CGFLOAT_MAX; [allAttributes enumerateObjectsUsingBlock:^(__kindof UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { CGFloat distance = CGRectGetMidX(proposedRect) - obj.center.x; if (ABS(distance) < minDistance) { proposedAttributes = obj; minDistance = distance; } }]; // Scroll always if (self.currentPage == proposedAttributes.indexPath.section) { if (velocity.x > 0 && self.currentPage < [self.collectionView numberOfSections]-1) self.currentPage += 1; else if (velocity.x < 0 && self.currentPage > 0) self.currentPage -= 1; } else { self.currentPage = proposedAttributes.indexPath.section; } return [self offsetAtCurrentPage]; } 

对于寻找基于垂直单元的分页的人来说(因为这正是我最初寻找的)。下面是我正在构build的库中的一部分代码(您需要将它放在UICollectionViewFlowLayout的子类中)。

码:

 /** This property enables paging per card. The default value is true. */ public var isPagingEnabled: Bool = true // MARK: cell paging override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { // MARK: If the property `isPagingEnabled` is set to false, we don't enable paging and thus return the current contentoffset guard isPagingEnabled else { let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) return latestOffset } var offsetAdjustment = CGFloat.greatestFiniteMagnitude let verticalOffset = proposedContentOffset.y let contentInset = collectionView!.contentInset.top let targetRect = CGRect(origin: CGPoint(x: 0, y: verticalOffset), size: self.collectionView!.bounds.size) for layoutAttributes in super.layoutAttributesForElements(in: targetRect)! { // MARK: we adjust for the contentInset let itemOffset = layoutAttributes.frame.origin.y - contentInset if (abs(itemOffset - verticalOffset) < abs(offsetAdjustment)) { offsetAdjustment = itemOffset - verticalOffset } } return CGPoint(x: proposedContentOffset.x, y: verticalOffset + offsetAdjustment) }