水平分页scrollview中缺少UICollectionView对齐逻辑

时间:2012-11-05 08:44:43

标签: iphone ios xcode cocoa-touch uicollectionview

我有一个UICollectionView,一切正常,直到我开始滚动。 这里有一些照片: enter image description here

你可以看到它很棒。当我开始滚动(启用分页)时,第一个在屏幕外显示: enter image description here

这就是问题所在。原始我的视图有3个视图,我想滚动并仅显示3个视图。但是当它滚动(启用分页)时,它隐藏了第一个视图的一点点,并显示下一页的下一个第一个视图的一点点。

这是一段视频,因为它有点难以解释: Video of the problem (Dropbox)

以下是我UICollectionView设置的图片: enter image description here

如果有人可以提供帮助,那将会很棒!

16 个答案:

答案 0 :(得分:76)

根本问题是Flow Layout不支持分页。要实现分页效果,您必须牺牲单元格之间的空间。并仔细计算单元格框架,并使其可以通过集合视图框架进行划分而不会留下余地。我会解释原因。

说出以下布局是你想要的。

enter image description here

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

因此,设置间距和插入后。您将获得以下布局。

enter image description here

滚动到下一页。你的细胞显然不符合你的预期。

enter image description here

使单元格间隔为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可能是一个好主意。

答案 1 :(得分: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];
}

答案 2 :(得分:19)

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

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

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

在我的情况下,我正在尝试从左到右分页,一次一个单元格,单元格之间没有空格。页面的每一个转弯都会从所需的位置引入一点点漂移,并且它似乎线性地累积。 每回合~10分。我意识到 10.0 是流布局中 minimumLineSpacing 的默认值。当我将它设置为 0.0 时,没有漂移,当我将其设置为边界宽度的一半时,每个页面都会移动额外的一半边界。

更改 minimumInteritemSpacing 无效

编辑 - 来自UICollectionViewFlowLayout的文档:

  

@property(nonatomic)CGFloat minimumLineSpacing;

     

讨论

     

...

     

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

     

此属性的默认值为10.0。

答案 3 :(得分:9)

enter image description here

以下文章的解决方案优雅而简单。主要思想是在collectionView顶部创建scrollView并传递所有contentOffset值。

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

应该通过实施这种方法来说:

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

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

答案 4 :(得分:7)

我知道这个问题已经过时了,但对于那些偶然发现这个问题的人来说。

要解决此问题,您需要将MinimumInterItemSpacing设置为0并减少内容的框架。

答案 5 :(得分:6)

我想我明白了这个问题。我会尝试让你理解它。

如果你仔细观察,那么你会看到这个问题只是逐渐发生而不只是在第一页滑动。

enter image description here

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

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

答案 6 :(得分:6)

@devdavid在flowLayout.minimumLineSpacing上点亮为零。

也可以在布局编辑器中完成,将行的最小间距设置为0:

Easier Way

答案 7 :(得分:3)

你自己推出UICollectionViewFlowLayout吗?

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

这可能有用(NB:UNTESTED!):

-(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);
}

答案 8 :(得分:3)

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

enter image description here

您应该将UIScrollView放在UICollectionView之上并给它们相同的尺寸:

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

然后配置集合视图的contentInset,例如:

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);
    }
}

答案 9 :(得分: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));

答案 10 :(得分: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);
}

这对我有用,所以我希望别人觉得它很有用!

答案 11 :(得分:1)

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

答案 12 :(得分:0)

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

UICollectionViewFlowLayout包含名为sectionInset的属性。因此,您可以将“插入”部分设置为您需要的任何内容,并使1页等于一个部分。因此,您的滚动应自动适合页面(=部分)

答案 13 :(得分:0)

我遇到了类似的分页问题。即使单元格插入全部为0且单元格的宽度和高度与UICollectionView完全相同,但分页也不合适。

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

对于将来的参考,我希望它有所帮助!

答案 14 :(得分:0)

我试图在3 x 3网格的单元格上进行水平分页时遇到了类似的问题,这些单元格具有分段插入和单元格&amp;行间距。

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

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

答案 15 :(得分:0)

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

((UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout).minimumLineSpacing = 0;