【问题标题】:UICollectionView - Remove extra spacingUICollectionView - 删除额外的间距
【发布时间】:2017-11-26 10:44:35
【问题描述】:

我有一个 iPad 应用程序,它使用 UICollectionView 来显示单元格网格。单元格的宽度完全相同,但高度不同(因为有些单元格的文本比其他单元格多)。

我遇到的唯一问题是较短的单元格最终会在上方/下方有额外的间距......这会创建一个看起来很糟糕的集合视图。它增加了额外的空间,不需要额外的空间。我尝试将边缘插图设置为 0,将最小行距设置为 0,但仍然无法正常工作。

这是我的收藏视图的样子:

这是我想要的样子:

这是我的流程布局代码:

[self.infoList registerClass:[InfoCell class] forCellWithReuseIdentifier:@"InfoCell"];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setItemSize:CGSizeMake(320, 160)];
[layout setScrollDirection:UICollectionViewScrollDirectionVertical];
[layout setMinimumInteritemSpacing:0];
[layout setMinimumLineSpacing:0];
[self.infoList setCollectionViewLayout:layout];

我也将边缘插图设置为 0:

-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsZero;
}

如何删除多余的不必要的空间?我发现可行的唯一方法是使所有单元格的高度完全相同,但这不是我想要的。有些单元格包含文本而不是其他单元格,所以我必须给它们额外的高度。

【问题讨论】:

    标签: ios objective-c uicollectionview uicollectionviewcell uicollectionviewlayout


    【解决方案1】:

    在我看来,你可以通过自定义UICollectionViewLayout来解决这个问题。你应该看看Collection View Programming Guide for iOS

    使用下面的代码,我假设我们每行有 2 行列,项目具有相同的宽度和不同的高度。

    const CGFloat kPadding                         = 5.f;
    const NSUInteger kNumberSmallCellALine         = 2;
    
    @interface CustomCollectionViewLayout : UICollectionViewLayout
    
    @property (nonatomic, strong) NSMutableArray *layoutAttributes;
    @property (nonatomic, assign) CGFloat contentHeight;
    
    @end
    
    @implementation CustomCollectionViewLayout
    
    // Use this method to perform the up-front calculations needed to provide layout information.
    - (void)prepareLayout{
      [super prepareLayout];
    
      if (!self.layoutAttributes){
        self.layoutAttributes = [[NSMutableArray alloc] init];
    
        // Calculate width of each item
        CGFloat cellWidth = (self.collectionView.frame.size.width - (kNumberSmallCellALine - 1) * kPadding) / kNumberSmallCellALine;
        // Default height of contentSize
        self.contentHeight = 0.f;
        // Height of column items on the left
        CGFloat leftColumnHeight = 0.f;
        // Height of column items on the right
        CGFloat rightColumnHeight = 0.f;
    
        for (int i = 0 ; i < [self.collectionView numberOfItemsInSection:0] ; i ++){
    
          // Height of item at index i
          CGFloat height = i * 20 + 20;
    
          CGFloat xPos, yPos = 0.f;
    
          if (i % 2 == 0) {
            // If item on the right
            xPos = 0.f;
            yPos = leftColumnHeight;
            leftColumnHeight += height + kPadding;
          } else {
            // Item on the left
            xPos = cellWidth + kPadding;
            yPos = rightColumnHeight;
            rightColumnHeight += height + kPadding;
          }
    
          // Update height of contentSize after adding new item
          self.contentHeight = MAX(leftColumnHeight, rightColumnHeight);
    
          UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
          attr.frame = CGRectMake(xPos,
                                  yPos,
                                  cellWidth,
                                  height);
          [self.layoutAttributes addObject:attr];
        }
      }
    }
    
    // Use this method to return the attributes for cells and views that are in the specified rectangle.
    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
      NSMutableArray *currentAttributes = [NSMutableArray new];
      for (UICollectionViewLayoutAttributes *attr in self.layoutAttributes) {
        if (CGRectIntersectsRect(attr.frame, rect))
        {
          [currentAttributes addObject:attr];
        }
      }
      return currentAttributes;
    }
    
    // Use this method to return the overall size of the entire content area based on your initial calculations.
    - (CGSize)collectionViewContentSize{
      return CGSizeMake(self.collectionView.frame.size.width, self.contentHeight);
    }
    
    @end
    

    使用

    CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] init];
    [collectionView setCollectionViewLayout:layout];
    

    这是一个解决您问题的演示。你应该改变每个项目的高度以适合你的情况。

    更多信息和更容易理解,可以查看My Demo Repo

    注意来自@Supertecnoboff

    如果您支持多种设备方向(例如:iPad 应用程序),则必须重新加载 layoutAttributes 数据。否则旋转设备时布局将不正确。然后,您需要在集合视图上调用 performBatchUpdates 以加载新的布局数据。

    【讨论】:

    • 完美运行!非常感谢你做的这些。我花了一段时间才弄清楚如何通过布局类计算适当的高度,但我设法让它工作。除了那一次修改之外,您的代码可以完美运行。再次感谢。
    • 对于查看此答案的其他人来说,还有一件事需要注意:如果您支持多个设备方向(例如:iPad 应用程序),则必须重新加载 layoutAttributes 数据。否则旋转设备时布局将不正确。然后,您需要在集合视图上调用 performBatchUpdates 以加载新的布局数据。
    • 感谢@Supertecnoboff。我已根据您的建议更新了我的答案。
    【解决方案2】:

    我相信你必须创建一个custom layout

    本质上,它包括以下方法的覆盖:

    1. - (void)prepareLayout;:执行所需的前期计算 提供布局信息。
    2. collectionViewContentSize:返回整体 根据您的初始计算确定整个内容区域的大小。
    3. layoutAttributesForElementsInRect::返回单元格和视图的属性 在指定的矩形内。

    prepareLayout 方法将有以下实现,这是一个粗略的实现,您可能需要根据需要对其进行调整。

    - (void) prepareLayout {
        if (self.cache.count == 0) {
            UIEdgeInsets insets = self.collectionView.contentInset;
            self.contentWidth = self.collectionView.bounds.size.width - (insets.left + insets.right);
    
            CGFloat columnWidth = self.contentWidth / self.numberOfColumns;
            NSMutableArray *xOffsets = [NSMutableArray new];
            NSMutableArray *yOffsets = [NSMutableArray new];
            for (int i = 0; i < self.numberOfColumns; i++) {
                [xOffsets addObject: @(i * columnWidth)];
                [yOffsets addObject: @(0)];
            }
    
            int column = 0;
            for (int i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) {
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
    
                CGFloat cellHeight = arc4random_uniform(200);
                CGFloat height = self.cellPadding * 2 + cellHeight;
                CGRect frame = CGRectMake((CGFloat)[[xOffsets objectAtIndex:column] floatValue],
                                          (CGFloat)[[yOffsets objectAtIndex:column] floatValue],
                                          columnWidth,
                                          height);
                CGRect insetFrame = CGRectInset(frame, self.cellPadding, self.cellPadding);
    
                UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
                attributes.frame = insetFrame;
                [self.cache addObject:attributes];
    
                self.contentHeight = MAX(self.contentHeight, CGRectGetMaxY(frame));
                [yOffsets replaceObjectAtIndex:column withObject:@([[yOffsets objectAtIndex:column]floatValue] + height)];
    
                column = column < (self.numberOfColumns - 1) ? (column + 1) : 0;
    
                }
            }
        }
    

    找到自定义类的gist

    【讨论】: