57

我有一个UICollectionView,它工作正常,直到我开始滚动。先来几张图: 在此处输入图像描述

如您所见,它很棒。当我开始滚动(启用分页)时,第一个有点离屏: 在此处输入图像描述

这就是问题。最初我的视图有 3 个视图,我只想滚动并显示 3 个视图。但是当它滚动(启用分页)时,它会隐藏一点第一个视图,并从下一页显示下一个第一个视图的一点点。

这是一个视频,因为它有点难以解释: 问题视频(Dropbox)

这是我的UICollectionView设置图片: 在此处输入图像描述

如果有人可以提供帮助,那就太好了!

4

16 回答 16

78

根本问题是 Flow Layout 不是为支持分页而设计的。要实现分页效果,您将不得不牺牲单元格之间的空间。并仔细计算单元格框架,使其可以被集合视图框架分割,没有余数。我会解释原因。

说下面的布局是你想要的。

在此处输入图像描述

请注意,最左边的边距(绿色)不是单元格间距的一部分。它由流布局部分插图确定。由于流布局不支持异构间距值。这不是一项微不足道的任务。

因此,在设置了间距和插图之后。以下布局是您将得到的。

在此处输入图像描述

滚动到下一页后。您的单元格显然没有按照您的预期对齐。

在此处输入图像描述

使单元格间距为 0 可以解决此问题。但是,如果您想要页面上的额外边距,它会限制您的设计,尤其是当边距与单元格间距不同时。它还要求视图框架必须能被单元框架整除。有时,如果您的视图框架不固定(考虑到旋转情况),这会很痛苦。

真正的解决方案是继承 UICollectionViewFlowLayout 并覆盖以下方法

- (CGSize)collectionViewContentSize
{
    // Only support single section for now.
    // Only support Horizontal scroll 
    NSUInteger count = [self.collectionView.dataSource collectionView:self.collectionView
                                               numberOfItemsInSection:0];

    CGSize canvasSize = self.collectionView.frame.size;
    CGSize contentSize = canvasSize;
    if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
    {
        NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
        NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
        NSUInteger page = ceilf((CGFloat)count / (CGFloat)(rowCount * columnCount));
        contentSize.width = page * canvasSize.width;
    }

    return contentSize;
}


- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize canvasSize = self.collectionView.frame.size;

    NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
    NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;

    CGFloat pageMarginX = (canvasSize.width - columnCount * self.itemSize.width - (columnCount > 1 ? (columnCount - 1) * self.minimumLineSpacing : 0)) / 2.0f;
    CGFloat pageMarginY = (canvasSize.height - rowCount * self.itemSize.height - (rowCount > 1 ? (rowCount - 1) * self.minimumInteritemSpacing : 0)) / 2.0f;

    NSUInteger page = indexPath.row / (rowCount * columnCount);
    NSUInteger remainder = indexPath.row - page * (rowCount * columnCount);
    NSUInteger row = remainder / columnCount;
    NSUInteger column = remainder - row * columnCount;

    CGRect cellFrame = CGRectZero;
    cellFrame.origin.x = pageMarginX + column * (self.itemSize.width + self.minimumLineSpacing);
    cellFrame.origin.y = pageMarginY + row * (self.itemSize.height + self.minimumInteritemSpacing);
    cellFrame.size.width = self.itemSize.width;
    cellFrame.size.height = self.itemSize.height;

    if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
    {
        cellFrame.origin.x += page * canvasSize.width;
    }

    return cellFrame;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes * attr = [super layoutAttributesForItemAtIndexPath:indexPath];
    attr.frame = [self frameForItemAtIndexPath:indexPath];
    return attr;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray * attrs = [NSMutableArray array];

    [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
        NSIndexPath * idxPath = attr.indexPath;
        CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
        if (CGRectIntersectsRect(itemFrame, rect))
        {
            attr = [self layoutAttributesForItemAtIndexPath:idxPath];
            [attrs addObject:attr];
        }
    }];

    return attrs;
}

注意,上面的代码片段只支持单节和水平滚动方向。但扩展并不难。

此外,如果您没有数百万个细胞。缓存那些 UICollectionViewLayoutAttributes 可能是个好主意。

于 2013-11-22T23:38:19.000 回答
28

您可以在 UICollectionView 上禁用分页并使用自定义页面宽度/偏移量实现自定义水平滚动/分页机制,如下所示:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    float pageWidth = 210;

    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, 0) animated:YES];
}
于 2014-03-16T20:14:19.350 回答
20

这个答案已经晚了,但我一直在玩这个问题,发现漂移的原因是行间距。如果您希望UICollectionView/FlowLayout以单元格宽度的精确倍数进行分页,则必须设置:

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
flowLayout.minimumLineSpacing = 0.0;

您不会认为行距在水平滚动中起作用,但显然确实如此。

就我而言,我正在尝试从左到右分页,一次一个单元格,单元格之间没有空间。每翻一页,就会从所需位置产生一点点漂移,而且它似乎是线性累积的。 每回合约 10.0 分。我意识到10.0是流布局中minimumLineSpacing的默认值。当我将其设置为0.0时,没有漂移,当我将其设置为边界宽度的一半时,每个页面都会漂移边界的一半。

更改minimumInteritemSpacing没有效果

编辑——来自UICollectionViewFlowLayout的文档:

@property (nonatomic) CGFloat minimumLineSpacing;

讨论

...

对于垂直滚动网格,此值表示连续行之间的最小间距。对于水平滚动网格,此值表示连续列之间的最小间距。 此间距不适用于页眉和第一行之间或最后一行和页脚之间的空间。

此属性的默认值为 10.0。

于 2013-09-22T15:04:11.347 回答
9

在此处输入图像描述

以下文章中的解决方案既优雅又简单。主要思想是通过传递所有 contentOffset 值在您的 collectionView 顶部创建滚动视图。

http://b2cloud.com.au/tutorial/uiscrollview-paging-size/

应该说通过实现这个方法:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;

我没有像 pagingEnabled = YES 那样实现流畅的动画。

于 2014-12-02T05:00:29.723 回答
8

我知道这个问题很老,但对于任何偶然发现这个问题的人来说。

要纠正此问题,您只需将 MinimumInterItemSpacing 设置为 0 并减小内容的框架。

于 2013-08-06T00:49:41.017 回答
7

@devdavid 出现在 flowLayout.minimumLineSpacing 为零。

也可以在布局编辑器中完成,将 Lines 的 Min Spacing 设置为 0:

更简单的方法

于 2016-08-30T11:51:55.093 回答
6

我想我理解这个问题。我也会试着让你明白。

如果您仔细观察,您会发现此问题只是逐渐发生,而不仅仅是在第一页滑动时发生。

在此处输入图像描述

如果我理解正确,目前在您的应用程序中,每个 UICollectionView 项目都是我们看到的那些圆角框,并且您在它们之间有一些恒定的偏移量/边距。这就是导致问题的原因。

相反,您应该做的是创建一个 UICollectionView 项目,该项目是整个视图宽度的 1/3,然后在其中添加该圆形图像视图。要参考图像,绿色应该是您的 UICollectionViewItem 而不是黑色。

于 2012-11-18T10:43:27.667 回答
3

你自己滚UICollectionViewFlowLayout吗?

如果是这样,添加-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity将帮助您计算滚动视图应该停止的位置。

这可能有效(注意:未经测试!):

-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
                             withScrollingVelocity:(CGPoint)velocity
{
  CGFloat offsetAdjustment = MAXFLOAT;
  CGFloat targetX = proposedContentOffset.x + self.minimumInteritemSpacing + self.sectionInset.left;

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

    if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
      CGFloat itemX = layoutAttributes.frame.origin.x;

      if (ABS(itemX - targetX) < ABS(offsetAdjustment)) {
        offsetAdjustment = itemX - targetX;
      }
    }
  }

  return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
于 2012-11-13T13:35:19.210 回答
3

我的答案基于答案https://stackoverflow.com/a/27242179/440168但更简单。

在此处输入图像描述

您应该放在UIScrollView上面UICollectionView并给它们相同的大小:

@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;

然后配置contentInsetcollection view,例如:

CGFloat inset = self.view.bounds.size.width*2/9;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);

contentSize滚动视图:

self.scrollView.contentSize = CGSizeMake(self.placesCollectionView.bounds.size.width*[self.collectionView numberOfItemsInSection:0],0);

不要忘记设置滚动视图的委托:

self.scrollView.delegate = self;

并实现主要魔法:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == self.scrollView) {
        CGFloat inset = self.view.bounds.size.width*2/9;
        CGFloat scale = (self.placesCollectionView.bounds.size.width-2*inset)/scrollView.bounds.size.width;
        self.collectionView.contentOffset = CGPointMake(scrollView.contentOffset.x*scale - inset, 0);
    }
}
于 2015-05-21T05:12:50.573 回答
2

UICollectionView的宽度应该是单元格大小宽度+左右插图的精确乘积。在您的示例中,如果单元格宽度为 96,则UICollectionView' 宽度应为(96 + 5 + 5) * 3 = 318. 或者,如果您希望保持 UICollectionView 的 320 宽度,则您的单元格大小宽度应为320 / 3 - 5 - 5 = 96.666.

如果这没有帮助,UICollectionView当应用程序运行时,您的宽度可能与 xib 文件中设置的不同。要检查这一点 - 添加一条NSLog语句以在运行时打印输出视图的大小:

NSLog(@"%@", NSStringFromCGRect(uiContentViewController.view.frame));
于 2012-11-18T14:08:22.393 回答
1

这是我遇到的同样的问题,我在另一篇文章中发布了我的解决方案,所以我会在这里再次发布。

我找到了一个解决方案,它涉及 UICollectionViewFlowLayout 的子类化。

我的 CollectionViewCell 大小为 302 X 457,我将最小行距设置为 18(每个单元格 9pix)

当您从该类扩展时,有一些方法需要被覆盖。其中之一是

  • (CGSize)collectionViewContentSize

在这种方法中,我需要将 UICollectionView 中的总宽度相加。这包括 ([datasource count] * widthOfCollectionViewCell) + ([datasource count] * 18)

这是我的自定义 UICollectionViewFlowLayout 方法....

-(id)init
{
    if((self = [super init])){

       self.itemSize = CGSizeMake(302, 457);
       self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
       self.minimumInteritemSpacing = 0.0f;
       self.minimumLineSpacing = 18.0f;
       [self setScrollDirection:UICollectionViewScrollDirectionHorizontal];
   }
    return self;
}



-(CGSize)collectionViewContentSize{
   return CGSizeMake((numCellsCount * 302)+(numCellsCount * 18), 457);
}

这对我有用,所以我希望其他人觉得它有用!

于 2013-03-14T05:02:04.240 回答
1

您还可以将视图的宽度设置为“320+间距”,然后将页面启用设置为是。每次都会滚动“320+间距”。我认为这是因为页面启用将滚动视图的宽度而不是屏幕的宽度。

于 2014-07-07T05:43:23.097 回答
0

我想我确实有解决这个问题的办法。但如果它是最好的,我不会。

UICollectionViewFlowLayout确实包含一个名为sectionInset. 因此,您可以将 Inset 部分设置为您需要的任何内容,并使 1 页等于一个部分。因此,您的滚动应该自动适合页面(=部分)

于 2013-02-06T10:48:11.120 回答
0

我在分页时遇到了类似的问题。即使单元格插图都是 0 并且单元格的宽度和高度大小与 完全相同UICollectionView,但分页还是不正确的。

我注意到的听起来像是这个版本(UICollectionViewiOS 6)中的一个错误:我可以看到,如果我UICollectionView使用宽度 = 310 像素或更高、高度 = 538 像素的 a,我就有麻烦了。但是,如果我将宽度减小到 300 像素(相同的高度),我就可以完美地工作了!

供将来参考,希望对您有所帮助!

于 2013-07-11T09:56:27.820 回答
0

当我尝试在具有部分插入和单元格和行间距的 3 x 3 单元格网格上进行水平分页时遇到了类似的问题。

我的答案(在尝试了许多建议之后——包括子类化 UICollectionViewFlowLayout 和各种 UIScrollView 委托解决方案)很简单。我只是使用 UICollectionView 中的部分,将我的数据集分成 9 个项目(或更少)的部分,并利用 numberOfSectionsInCollectionView 和 numberOfItemsInSection UICollectionView 数据源方法。

UICollectionView 的水平分页现在可以很好地工作。我向目前在类似情况下撕毁头发的任何人推荐这种方法。

于 2014-07-31T14:34:33.693 回答
0

如果您使用 UICollectionView 的默认流布局并且不希望每个单元格之间有任何空间,则可以通过以下方式将其 miniumumLineSpacing 属性设置为 0:

((UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout).minimumLineSpacing = 0;
于 2015-09-13T04:44:49.947 回答