Implementing Section Borders with JJCollectionViewRoundFlowLayout
You configure section borders by returning a JJCollectionViewRoundConfigModel with borderWidth and borderColor values from the delegate method collectionView:layout:configModelForSectionAtIndex:, which the layout applies as native CALayer borders on the section's decoration view.
The JJCollectionViewRoundFlowLayout library (available at kingjiajie/jjcollectionviewroundflowlayout) extends UICollectionViewFlowLayout to draw rounded backgrounds with optional borders around entire sections. Unlike subclassing cells, this approach uses decoration views—separate from the cell hierarchy—to render visual containers, ensuring borders appear behind content and respond to layout changes automatically.
How the Border System Works
Borders in JJCollectionViewRoundFlowLayout are rendered as native CALayer properties on decoration views that sit behind each collection view section. The layout does not hardcode border values; instead, it queries your delegate for a configuration model per section.
The configuration model (JJCollectionViewRoundConfigModel) defines border appearance through two key properties defined in JJCollectionViewRoundConfigModel.h (lines 14‑19):
borderWidth: The width of the border line in points (CGFloat)borderColor: The color of the border (UIColor)
When borderWidth is greater than zero, the layout creates a decoration view attribute that stores these values. The actual rendering occurs in JJCollectionReusableView (the decoration view class), which applies the values to its backing layer during applyLayoutAttributes:.
Configuring Borders via the Delegate Protocol
To enable borders, your view controller must conform to JJCollectionViewDelegateRoundFlowLayout and implement the required configuration method. The layout calls this method during prepareLayout to retrieve section-specific settings.
The delegate method signature is:
- (JJCollectionViewRoundConfigModel *)collectionView:(UICollectionView *)collectionView
layout:(JJCollectionViewLayout *)collectionViewLayout
configModelForSectionAtIndex:(NSInteger)section;
Inside JJCollectionViewRoundFlowLayout.m (lines 495‑497), the layout checks for this implementation:
if ([delegate respondsToSelector:@selector(collectionView:layout:configModelForSectionAtIndex:)]) {
attr.myConfigModel = [delegate collectionView:self.collectionView
layout:self
configModelForSectionAtIndex:section];
}
If you return a model with default values (zero width), no border appears. To render a visible border, explicitly set the width and color properties:
#pragma mark - JJCollectionViewDelegateRoundFlowLayout
- (JJCollectionViewRoundConfigModel *)collectionView:(UICollectionView *)collectionView
layout:(JJCollectionViewLayout *)layout
configModelForSectionAtIndex:(NSInteger)section {
JJCollectionViewRoundConfigModel *model = [[JJCollectionViewRoundConfigModel alloc] init];
// Background configuration
model.backgroundColor = [UIColor systemBackgroundColor];
model.cornerRadius = 12.0;
// Border configuration
model.borderWidth = 1.5;
model.borderColor = [UIColor separatorColor];
return model;
}
How Borders Are Rendered on Screen
The border is not drawn via Core Graphics in drawRect:; instead, it uses the hardware-accelerated CALayer border properties for optimal scrolling performance.
When the layout finishes calculations in prepareLayout, it creates JJCollectionViewRoundLayoutAttributes containing the myConfigModel. During layoutAttributesForElementsInRect:, these attributes are returned to the collection view, which instantiates or updates the corresponding JJCollectionReusableView decoration views.
In JJCollectionViewRoundFlowLayout.m (lines 106‑114), the layout attributes are applied to the view's layer:
// Inside JJCollectionReusableView (decoration view) implementation
view.layer.borderWidth = model.borderWidth;
if (@available(iOS 13.0, *)) {
// Respect dark mode and trait collections automatically
view.layer.borderColor = [model.borderColor resolvedColorWithTraitCollection:self.traitCollection].CGColor;
} else {
view.layer.borderColor = model.borderColor.CGColor;
}
This implementation ensures that:
- Dynamic colors (like
UIColor.separatorColor) automatically adapt to Light/Dark mode on iOS 13+ - Performance remains high because the system composites the border in the render server, not the CPU
- Animation works seamlessly with collection view updates
Customizing Border Position with Edge Insets
By default, the border aligns with the section's edge insets calculated from the flow layout's sectionInset. To create a border that is inset relative to the background (or extends beyond it), implement the optional delegate method collectionView:layout:borderEdgeInsertsForSectionAtIndex:.
This method returns UIEdgeInsets that shrink or expand the decoration view's frame independently of the content inset:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
layout:(JJCollectionViewLayout *)layout
borderEdgeInsertsForSectionAtIndex:(NSInteger)section {
// Inset the border 8pt inside the section background
return UIEdgeInsetsMake(8, 8, 8, 8);
}
Behind the scenes, JJCollectionViewRoundFlowLayout.m stores this value in the layout attributes (lines 390‑398) and applies it when calculating the final decoration view frame (lines 800‑808):
// Store the custom inset in the attribute
attr.borderEdgeInsets = userCustomSectionInset;
// Apply inset to the decoration view frame
sectionFrame.origin.x += userCustomSectionInset.left;
sectionFrame.origin.y += userCustomSectionInset.top;
sectionFrame.size.width -= (userCustomSectionInset.left + userCustomSectionInset.right);
sectionFrame.size.height -= (userCustomSectionInset.top + userCustomSectionInset.bottom);
Positive values shrink the border toward the center; negative values extend it outward beyond the background bounds.
Complete Implementation Example
Here is a complete view controller implementation that configures a collection view with rounded sections, background colors, and 1-point separators between sections:
#import "JJCollectionViewRoundFlowLayout.h"
@interface ViewController () <UICollectionViewDataSource, JJCollectionViewDelegateRoundFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
JJCollectionViewRoundFlowLayout *layout = [[JJCollectionViewRoundFlowLayout alloc] init];
layout.delegate = self; // Required for border configuration
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds
collectionViewLayout:layout];
self.collectionView.dataSource = self;
[self.view addSubview:self.collectionView];
}
#pragma mark - JJCollectionViewDelegateRoundFlowLayout
- (JJCollectionViewRoundConfigModel *)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
configModelForSectionAtIndex:(NSInteger)section {
JJCollectionViewRoundConfigModel *model = [[JJCollectionViewRoundConfigModel alloc] init];
// Visual styling
model.backgroundColor = [UIColor secondarySystemBackgroundColor];
model.cornerRadius = 8.0;
// Border styling
model.borderWidth = 1.0;
model.borderColor = [UIColor systemGray3Color];
return model;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
borderEdgeInsertsForSectionAtIndex:(NSInteger)section {
// Keep border 4pt inside the background edge
return UIEdgeInsetsMake(4, 4, 4, 4);
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 3;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 10;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell"
forIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor systemOrangeColor];
return cell;
}
@end
Summary
- Delegate Protocol: Conform to
JJCollectionViewDelegateRoundFlowLayoutand implementcollectionView:layout:configModelForSectionAtIndex:to return configuration models. - Border Properties: Set
borderWidthandborderColoron theJJCollectionViewRoundConfigModelinstance to enable nativeCALayerborders. - iOS Version Support: The implementation automatically uses
resolvedColorWithTraitCollection:on iOS 13+ to support dynamic colors, falling back to standardCGColoron earlier versions. - Positioning Control: Use
collectionView:layout:borderEdgeInsertsForSectionAtIndex:to inset or outset the border relative to the section background, as calculated inJJCollectionViewRoundFlowLayout.m(lines 800‑808). - Performance: Borders render using hardware-accelerated layer properties, not CPU-bound drawing, maintaining smooth scrolling performance.
Frequently Asked Questions
How do I remove the border from a specific section while keeping it on others?
Return a JJCollectionViewRoundConfigModel with borderWidth set to 0.0 (or leave it at the default zero) for sections that should not display a border. The layout checks model.borderWidth before applying layer properties, so zero width effectively disables the border for that section while preserving the rounded background.
Does the border color automatically support Dark Mode?
Yes. The implementation in JJCollectionViewRoundFlowLayout.m uses resolvedColorWithTraitCollection: when running on iOS 13 or later, which resolves dynamic colors (like UIColor.separatorColor or UIColor.systemGrayColor) against the current trait collection before converting to CGColor. On iOS 12 and earlier, it falls back to the standard CGColor conversion, so static colors work as expected.
Why are my borders appearing clipped or outside the visible bounds?
This typically occurs when the borderEdgeInsets are negative (extending outward) but the collection view's bounds or contentInset do not provide sufficient space. Ensure your collection view has clipsToBounds disabled if you intend borders to extend visibly beyond cell boundaries, or verify that your borderEdgeInsertsForSectionAtIndex: implementation returns positive insets if you want the border contained within the section background.
Can I animate the border color or width when the collection view updates?
Yes. Because the border is rendered via standard CALayer properties, you can animate changes by calling reloadSections: or invalidating the layout with animation. For custom transitions, modify the model values and trigger invalidateLayout—the decoration view will pick up the new borderWidth and borderColor values and animate if wrapped in a UIView animation block, though precise control requires accessing the decoration view directly via the layout's supplementary view APIs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →