I wanna making 2pt black separators in UICollectionView
for our new app. Screenshot from our app is below. We couldn't use UITableView
, because we have custom insert/delete animations, scrolling and parallax effects and so on.
I started with three ideas how to make it:
minimumLineSpacing
, thus we will see background in spaces between cellsFirst two variants were rejected because ideologic inconsistency, custom animations and having content below collection. Also I already have a custom layout.
I will describe the steps with a custom subclass of UICollectionViewFlowLayout
.
Implement custom UICollectionReusableView
subclass.
@interface FLCollectionSeparator : UICollectionReusableView @end @implementation FLCollectionSeparator - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor blackColor]; } return self; } - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { self.frame = layoutAttributes.frame; } @end
Say layout to use custom decorations. Also make line spacing between cells.
UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*) self.newsCollection.collectionViewLayout; [layout registerClass:[FLCollectionSeparator class] forDecorationViewOfKind:@"Separator"]; layout.minimumLineSpacing = 2;
In custom UICollectionViewFlowLayout
subclass we should return UICollectionViewLayoutAttributes
for decorations from layoutAttributesForElementsInRect
.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { ... collect here layout attributes for cells ... NSMutableArray *decorationAttributes = [NSMutableArray array]; NSArray *visibleIndexPaths = [self indexPathsOfSeparatorsInRect:rect]; // will implement below for (NSIndexPath *indexPath in visibleIndexPaths) { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForDecorationViewOfKind:@"Separator" atIndexPath:indexPath]; [decorationAttributes addObject:attributes]; } return [layoutAttributesArray arrayByAddingObjectsFromArray:decorationAttributes]; }
For visible rect we should return visible decorations index pathes.
- (NSArray*)indexPathsOfSeparatorsInRect:(CGRect)rect { NSInteger firstCellIndexToShow = floorf(rect.origin.y / self.itemSize.height); NSInteger lastCellIndexToShow = floorf((rect.origin.y + CGRectGetHeight(rect)) / self.itemSize.height); NSInteger countOfItems = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0]; NSMutableArray* indexPaths = [NSMutableArray new]; for (int i = MAX(firstCellIndexToShow, 0); i <= lastCellIndexToShow; i++) { if (i < countOfItems) { [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]]; } } return indexPaths; }
Also we should implement layoutAttributesForDecorationViewOfKind
.
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath]; CGFloat decorationOffset = (indexPath.row + 1) * self.itemSize.height + indexPath.row * self.minimumLineSpacing; layoutAttributes.frame = CGRectMake(0.0, decorationOffset, self.collectionViewContentSize.width, self.minimumLineSpacing); layoutAttributes.zIndex = 1000; return layoutAttributes; }
Sometimes I found that this solution gives visual glitches with decorations appearance, which was fixed with implementing initialLayoutAttributesForAppearingDecorationElementOfKind
.
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath { UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath]; return layoutAttributes; }
That's all. Not too much code but done right.
Quick solution in Swift
1. Create CustomFlowLayout.swift file and paste next code
import UIKit private let separatorDecorationView = "separator" final class CustomFlowLayout: UICollectionViewFlowLayout { override func awakeFromNib() { super.awakeFromNib() register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView) } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let layoutAttributes = super.layoutAttributesForElements(in: rect) ?? [] let lineWidth = self.minimumLineSpacing var decorationAttributes: [UICollectionViewLayoutAttributes] = [] // skip first cell for layoutAttribute in layoutAttributes where layoutAttribute.indexPath.item > 0 { let separatorAttribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: separatorDecorationView, with: layoutAttribute.indexPath) let cellFrame = layoutAttribute.frame separatorAttribute.frame = CGRect(x: cellFrame.origin.x, y: cellFrame.origin.y - lineWidth, width: cellFrame.size.width, height: lineWidth) separatorAttribute.zIndex = Int.max decorationAttributes.append(separatorAttribute) } return layoutAttributes + decorationAttributes } } private final class SeparatorView: UICollectionReusableView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .red } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { self.frame = layoutAttributes.frame } }
2. Setup custom flow
In the interface builder select your UICollectionViewFlow and set our new class name CustomFlowLayout
3. Change a separator color
In SeparatorView you can change the color of separator in init
4. Change a height of the separator
You can do it in two different ways
Min Spacing for Lines
OR
In the code. Set value for minimumLineSpacing
override func awakeFromNib() { super.awakeFromNib() register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView) minimumLineSpacing = 2 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With