Search code examples
iosobjective-cuicollectionviewuicollectionviewcelluicollectionviewlayout

UICollectionView - Remove extra spacing


I have an iPad application that uses to UICollectionView to display a grid of cells. The cells have the exact same width but different heights (as some have more text than others).

The only problem I am having is that the cells that are shorter, end up having extra spacing above/below.... this creates a bad looking collection view. It adds extra space, where no extra space is needed. I have tried to set the edge insets to 0 and the minimum line spacing to 0, but it still doesn't quite work.

Here is what my collection view looks like:

enter image description here

And here is what I want it to look like:

enter image description here

Here is my flow layout code:

[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];

I am setting the edge insets to 0 as well:

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

How can I remove the extra unnecessary space? The only way I have found that works, is to make all the cells the exact same height, but that's not what I want. Some cells contains for text than others, so I have to give them extra height.


Solution

  • In my opinion, you can resolve this problem by custom UICollectionViewLayout. You should take a look at Collection View Programming Guide for iOS.

    With the code below, I assume we have 2 row column each row, items have same width and different height.

    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
    

    Using

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

    It's a demo to resolve your issue. You should change the height of each item for being suitable with your situation.

    For more information and easier understanding, you can check My Demo Repo

    NOTE from @Supertecnoboff

    You have to reload the layoutAttributes data if you are supporting multiple device orientations (eg: an iPad app). Otherwise the layout will be incorrect when rotating the device. You then need to call performBatchUpdates on the collection view to load in the new layout data.