UICollectionView自定义行分隔符

我想为我们的新应用程序在UICollectionView 2pt黑色分隔符 。 下面是我们应用的屏幕截图。 我们不能使用UITableView ,因为我们有自定义的插入/删除animation,滚动和视差效果等等。

例

我从三个想法开始如何做到这一点:

  • 在细胞内部实施这些分离器
  • 使用带有minimumLineSpacing 纯黑色背景 ,因此我们将在单元格之间的空格中看到背景
  • 使用自定义布局和实现这个分隔符作为装饰

前两个变种被拒绝,因为意识形态的不一致,自定义animation和内容低于收集。 另外我已经有一个自定义的布局。

我将使用UICollectionViewFlowLayout的自定义子类来描述这些步骤。

–1–

实现自定义的UICollectionReusableView子类。

 @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 

–2–

说布局使用自定义装饰。 也让单元格之间的行距。

 UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*) self.newsCollection.collectionViewLayout; [layout registerClass:[FLCollectionSeparator class] forDecorationViewOfKind:@"Separator"]; layout.minimumLineSpacing = 2; 

–3–

在自定义的UICollectionViewFlowLayout子类中,我们应该从layoutAttributesForElementsInRect返回装饰的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]; } 

–4–

对于可见矩形,我们应该返回可见的装饰索引轨迹。

 - (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; } 

–5–

我们也应该实现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; } 

–6–

有时我发现这个解决scheme给装饰外观带来了视觉上的毛病,这是通过实现initialLayoutAttributesForAppearingDecorationElementOfKind

 - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath { UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath]; return layoutAttributes; } 

就这样。 没有太多的代码,但做对了。

Anton提出了很好的build议,但我认为FlowLayout子类中的实现可以更简单。 因为 – (NSArray *)layoutAttributesForElementsInRect:(CGRect)的超级实现rect已经返回了单元格的布局属性,包括它们的框架和indexPath,所以你有足够的信息来通过覆盖这个方法来计算分隔符的框架并反省单元布局属性:

 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *layoutAttributesArray = [super layoutAttributesForElementsInRect:rect]; CGFloat lineWidth = self.minimumLineSpacing; NSMutableArray *decorationAttributes = [[NSMutableArray alloc] initWithCapacity:layoutAttributesArray.count]; for (UICollectionViewLayoutAttributes *layoutAttributes in layoutAttributesArray) { //Add separator for every row except the first NSIndexPath *indexPath = layoutAttributes.indexPath; if (indexPath.item > 0) { UICollectionViewLayoutAttributes *separatorAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:kCellSeparatorKind withIndexPath:indexPath]; CGRect cellFrame = layoutAttributes.frame; //In my case I have a horizontal grid, where I need vertical separators, but the separator frame can be calculated as needed //eg top, or both top and left separatorAttributes.frame = CGRectMake(cellFrame.origin.x - lineWidth, cellFrame.origin.y, lineWidth, cellFrame.size.height); separatorAttributes.zIndex = 1000; [decorationAttributes addObject:separatorAttributes]; } } return [layoutAttributesArray arrayByAddingObjectsFromArray:decorationAttributes]; } 

感谢Anton和Werner,他们都帮助我 – 我已经帮你做了一个拖放解决scheme,作为UICollectionView一个类别,我认为我会分享结果:

UICollectionView + Separators.h

 #import <UIKit/UIKit.h> @interface UICollectionView (Separators) @property (nonatomic) BOOL sep_useCellSeparators; @property (nonatomic, strong) UIColor *sep_separatorColor; @end 

UICollectionView + Separators.m

 #import "UICollectionView+Separators.h" @import ObjectiveC; #pragma mark - #pragma mark - @interface UICollectionViewLayoutAttributes (SEPLayoutAttributes) @property (nonatomic, strong) UIColor *sep_separatorColor; @end @implementation UICollectionViewLayoutAttributes (SEPLayoutAttributes) - (void)setSep_separatorColor:(UIColor *)sep_separatorColor { objc_setAssociatedObject(self, @selector(sep_separatorColor), sep_separatorColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (UIColor *)sep_separatorColor { return objc_getAssociatedObject(self, @selector(sep_separatorColor)); } @end #pragma mark - #pragma mark - @interface SEPCollectionViewCellSeparatorView : UICollectionReusableView @end @implementation SEPCollectionViewCellSeparatorView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor blackColor]; } return self; } - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { self.frame = layoutAttributes.frame; if (layoutAttributes.sep_separatorColor != nil) { self.backgroundColor = layoutAttributes.sep_separatorColor; } } @end #pragma mark - #pragma mark - static NSString *const kCollectionViewCellSeparatorReuseId = @"kCollectionViewCellSeparatorReuseId"; @implementation UICollectionViewFlowLayout (SEPCellSeparators) #pragma mark - Setters/getters - (void)setSep_separatorColor:(UIColor *)sep_separatorColor { objc_setAssociatedObject(self, @selector(sep_separatorColor), sep_separatorColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self invalidateLayout]; } - (UIColor *)sep_separatorColor { return objc_getAssociatedObject(self, @selector(sep_separatorColor)); } - (void)setSep_useCellSeparators:(BOOL)sep_useCellSeparators { if (self.sep_useCellSeparators != sep_useCellSeparators) { objc_setAssociatedObject(self, @selector(sep_useCellSeparators), @(sep_useCellSeparators), OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self registerClass:[SEPCollectionViewCellSeparatorView class] forDecorationViewOfKind:kCollectionViewCellSeparatorReuseId]; [self invalidateLayout]; } } - (BOOL)sep_useCellSeparators { return [objc_getAssociatedObject(self, @selector(sep_useCellSeparators)) boolValue]; } #pragma mark - Method Swizzling + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(layoutAttributesForElementsInRect:); SEL swizzledSelector = @selector(swizzle_layoutAttributesForElementsInRect:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (NSArray<UICollectionViewLayoutAttributes *> *)swizzle_layoutAttributesForElementsInRect:(CGRect)rect { NSArray *layoutAttributesArray = [self swizzle_layoutAttributesForElementsInRect:rect]; if (self.sep_useCellSeparators == NO) { return layoutAttributesArray; } CGFloat lineSpacing = self.minimumLineSpacing; NSMutableArray *decorationAttributes = [[NSMutableArray alloc] initWithCapacity:layoutAttributesArray.count]; for (UICollectionViewLayoutAttributes *layoutAttributes in layoutAttributesArray) { NSIndexPath *indexPath = layoutAttributes.indexPath; if (indexPath.item > 0) { id <UICollectionViewDelegateFlowLayout> delegate = (id <UICollectionViewDelegateFlowLayout>)self.collectionView.delegate; if ([delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]) { lineSpacing = [delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:indexPath.section]; } UICollectionViewLayoutAttributes *separatorAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:kCollectionViewCellSeparatorReuseId withIndexPath:indexPath]; CGRect cellFrame = layoutAttributes.frame; if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) { separatorAttributes.frame = CGRectMake(cellFrame.origin.x - lineSpacing, cellFrame.origin.y, lineSpacing, cellFrame.size.height); } else { separatorAttributes.frame = CGRectMake(cellFrame.origin.x, cellFrame.origin.y - lineSpacing, cellFrame.size.width, lineSpacing); } separatorAttributes.zIndex = 1000; separatorAttributes.sep_separatorColor = self.sep_separatorColor; [decorationAttributes addObject:separatorAttributes]; } } return [layoutAttributesArray arrayByAddingObjectsFromArray:decorationAttributes]; } @end #pragma mark - #pragma mark - @implementation UICollectionView (Separators) - (UICollectionViewFlowLayout *)sep_flowLayout { if ([self.collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]) { return (UICollectionViewFlowLayout *)self.collectionViewLayout; } return nil; } - (void)setSep_separatorColor:(UIColor *)sep_separatorColor { [self.sep_flowLayout setSep_separatorColor:sep_separatorColor]; } - (UIColor *)sep_separatorColor { return [self.sep_flowLayout sep_separatorColor]; } - (void)setSep_useCellSeparators:(BOOL)sep_useCellSeparators { [self.sep_flowLayout setSep_useCellSeparators:sep_useCellSeparators]; } - (BOOL)sep_useCellSeparators { return [self.sep_flowLayout sep_useCellSeparators]; } @end 

使用Objective-C运行时和一些调整,单元格分隔符可以添加几行到任何现有的UICollectionView其布局是从/从UICollectionViewFlowLayoutinheritance。

用法示例:

 #import "UICollectionView+Separators.h" ... self.collectionView.sep_useCellSeparators = YES; self.collectionView.sep_separatorColor = [UIColor blackColor]; 

一对夫妇笔记:

  • 可以使用collectionView:layout:minimumLineSpacingForSectionAtIndex: minimumLineSpacing如果不实现,则在minimumLineSpacing回退, minimumLineSpacing确定每个段的分隔符高度/宽度
  • build立处理水平或垂直滚动​​方向

希望能帮助到你

在Swift中快速解决

1.创buildCustomFlowLayout.swift文件并粘贴下一个代码

 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.设置自定义stream程

在界面生成器中select您的UICollectionViewFlow并设置我们的新类名称CustomFlowLayout

3.更改分隔符的颜色

在SeparatorView中,可以更改init中分隔符的颜色

4.更改分隔符的高度

你可以用两种不同的方法做到这一点

  • 在storyboboard。 更改线的属性Min Spacing for Lines

要么

  • 在代码中。 设置minimumLineSpacing

     override func awakeFromNib() { super.awakeFromNib() register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView) minimumLineSpacing = 2 } 

这里是Anton Gaenko的版本,但在C#中实现,这可能对Xamarin用户有用:

 [Register(nameof(FLCollectionSeparator))] public class FLCollectionSeparator : UICollectionReusableView { public FLCollectionSeparator(CGRect frame) : base(frame) { this.BackgroundColor = UIColor.Black; } public FLCollectionSeparator(IntPtr handle) : base(handle) { this.BackgroundColor = UIColor.Black; } public override void ApplyLayoutAttributes(UICollectionViewLayoutAttributes layoutAttributes) { this.Frame = layoutAttributes.Frame; } } [Register(nameof(UILinedSpacedViewFlowLayout))] public class UILinedSpacedViewFlowLayout : UICollectionViewFlowLayout { public const string SeparatorAttribute = "Separator"; private static readonly NSString NSSeparatorAttribute = new NSString(SeparatorAttribute); public UILinedSpacedViewFlowLayout() : base() { this.InternalInit(); } public UILinedSpacedViewFlowLayout(NSCoder coder) : base (coder) { this.InternalInit(); } protected UILinedSpacedViewFlowLayout(NSObjectFlag t) : base(t) { this.InternalInit(); } private void InternalInit() { this.RegisterClassForDecorationView(typeof(FLCollectionSeparator), NSSeparatorAttribute); } public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect) { return LayoutAttributesForElementsInRect_internal(rect).ToArray(); } private IEnumerable<UICollectionViewLayoutAttributes> LayoutAttributesForElementsInRect_internal(CGRect rect) { foreach (var baseDecorationAttr in base.LayoutAttributesForElementsInRect(rect)) { yield return baseDecorationAttr; } foreach (var indexPath in this.IndexPathsOfSeparatorsInRect(rect)) { yield return this.LayoutAttributesForDecorationView(NSSeparatorAttribute, indexPath); } } private IEnumerable<NSIndexPath> IndexPathsOfSeparatorsInRect(CGRect rect) { int firstCellIndexToShow = (int)(rect.Y / this.ItemSize.Height); int lastCellIndexToShow = (int)((rect.Y + rect.Height) / this.ItemSize.Height); int countOfItems = (int)this.CollectionView.DataSource.GetItemsCount(this.CollectionView, 0); for (int i = Math.Max(firstCellIndexToShow, 0); i <= lastCellIndexToShow; i++) { if (i < countOfItems) { yield return NSIndexPath.FromItemSection(i, 0); } } } public override UICollectionViewLayoutAttributes LayoutAttributesForDecorationView(NSString kind, NSIndexPath indexPath) { UICollectionViewLayoutAttributes layoutAttributes = base.LayoutAttributesForDecorationView(kind, indexPath); var decorationOffset = (indexPath.Row + 1) * this.ItemSize.Height + indexPath.Row * this.MinimumLineSpacing + this.HeaderReferenceSize.Height; layoutAttributes = UICollectionViewLayoutAttributes.CreateForDecorationView(kind, indexPath); layoutAttributes.Frame = new CGRect(0, decorationOffset, this.CollectionViewContentSize.Width, this.MinimumLineSpacing); layoutAttributes.ZIndex = 1000; return layoutAttributes; } public override UICollectionViewLayoutAttributes InitialLayoutAttributesForAppearingDecorationElement(NSString elementKind, NSIndexPath decorationIndexPath) { return base.InitialLayoutAttributesForAppearingDecorationElement(elementKind, decorationIndexPath); } }