Becca Royal-Gordon
Becca Royal-Gordon

Reputation: 17861

UICollectionView cell borders in a flow layout

I'm using UICollectionView to lay out a bunch of cells that are sectioned by first letter of their title. Each cell should have a very thin border around it, and the section headers should have borders above and below. Here's my current prototype:

Grid of large equally-sized cells, four to a row, with slim light gray headings between sections. Narrow gray borders show the edges of the cells and dividers.

I achieve the current appearance with the following rules:

  1. Stroke the right and bottom edge of each cell.
  2. Stroke the bottom edge of each section heading.

This is very close to what I want, but there are two defects:

  1. If the line before a section heading isn't full, then the border along the top of the heading stops short of the right edge of the screen.
  2. It's not visible in this screenshot, but if a line is full, the right border of the last cell in the line is still drawn, which looks a little odd against the edge of the screen.

My best idea to fix this is to somehow tell each cell if it's in the last row of a section or the last cell in a row; then the cell would turn off the offending borders, section headings would draw a top border as well as a bottom, and everything would be hunky-dory. I don't know how to achieve that, though.

Any thoughts on how to manage that, or another way to get the look I'm going for? I'm currently using a UICollectionViewFlowLayout.

Upvotes: 4

Views: 11375

Answers (3)

wgr
wgr

Reputation: 513

enter image description here

I solve this problem in a simple way. I didn't add boarder to cell, instead I add a label with boarder into the cell. For the first column, the frame of the label is the same with the cell. For the other label, I set the x coordinate -0.5 to make their boarder overlap. Hope it helps.

Here is the code:

- (UICollectionViewCell *) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];

    // Then use it
    UILabel *label = nil;
    if (cell.contentView.subviews.count > 0) {
        label = cell.contentView.subviews[0];
    } else {
        label = [[UILabel alloc] init];
    }
    label.text = @"北京";

    [label setTextAlignment:NSTextAlignmentCenter];
    [label setFont:[UIFont systemFontOfSize:14]];
    [label setCenter:cell.contentView.center];


    CGRect frame = label.frame;
    if (indexPath.row%4 == 0) {
        frame.origin.x = 0;
    } else {
        frame.origin.x = -0.5;
    }

    frame.origin.y = 0;
    frame.size.width = self.collectionView.frame.size.width / 4;
    frame.size.height = self.collectionView.frame.size.height / 9;

    [label setFrame:frame];

    if (cell.contentView.subviews.count == 0) {
        [[cell contentView] addSubview:label];
    }

    label.layer.borderWidth = 0.5;
    label.layer.borderColor = [[UIColor lightGrayColor] CGColor];


    cell.backgroundColor = [UIColor whiteColor];

    return cell;
}

Upvotes: 3

Becca Royal-Gordon
Becca Royal-Gordon

Reputation: 17861

I ended up subclassing UICollectionViewFlowLayout and applying several heuristics after the flow layout had calculated the attributes for each cell:

  1. If center.y is equal to center.y of the last item in the section, the cell is in the last row of the section.
  2. If CGRectGetMaxY(frame) is equal to CGRectGetMaxY(self.collectionView.bounds), then the cell is agains the right edge of the collection view.

I then stored the results of these calculations in a subclass of UICollectionViewLayoutAttributes, and wrote a UICollectionViewCell subclass whose -applyLayoutAttributes: method would adjust the borders its background view draws based on the additional properties.

I've put the whole mess into a fairly enormous gist so you can see exactly what I did. Happy hacking.

Upvotes: 4

Timothy Moose
Timothy Moose

Reputation: 9915

My best idea to fix this is to somehow tell each cell if it's in the last row of a section or the last cell in a row; then the cell would turn off the offending borders, section headings would draw a top border as well as a bottom, and everything would be hunky-dory. I don't know how to achieve that, though.

What you describe is more or less what I did in a similar scenario. I added a border property to my cell:

typedef NS_OPTIONS(NSInteger, TLGridBorder) {
    TLGridBorderNone = 0,
    TLGridBorderTop = 1 << 0,
    TLGridBorderRight = 1 << 1,
    TLGridBorderBottom = 1 << 2,
    TLGridBorderLeft = 1 << 3,
    TLGridBorderAll = TLGridBorderTop | TLGridBorderRight | TLGridBorderBottom | TLGridBorderLeft,
};

@interface TLGridCellView : UIView
@property (nonatomic) TLGridBorder border;
@end

Then I set the border in my view controller's cell configuration:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    TLGridCellView *cell = ...;

    if (indexPath.item == self collectionView:collectionView numberOfItemsInSection:indexPath.section - 1) {
        cell.border = TLGridBorderLeft;
    } else {
        cell.border = TLGridBorderLeft | TLGridBorderRight;
    }

    return cell;
}

Upvotes: 3

Related Questions