Ios UICollectionView自定义行分隔符

Ios UICollectionView自定义行分隔符,ios,objective-c,uicollectionview,uicollectionviewlayout,uicollectionreusableview,Ios,Objective C,Uicollectionview,Uicollectionviewlayout,Uicollectionreusableview,我想在UICollectionView中为我们的新应用程序制作2pt黑色分隔符。下面是我们应用程序的截图。我们无法使用UITableView,因为我们有自定义的插入/删除动画、滚动和视差效果等 我从三个想法开始讲述如何做到这一点: 将这些分离器安装在单元内部的右侧 使用带有最小行间距的纯黑色背景,这样我们可以在单元格之间的空格中看到背景 使用自定义布局并将此分隔符用作装饰 前两个变体被拒绝,因为意识形态不一致、自定义动画和内容低于集合。此外,我已经有一个自定义布局 我将使用自定义子类UICo

我想在
UICollectionView
中为我们的新应用程序制作2pt黑色分隔符。下面是我们应用程序的截图。我们无法使用
UITableView
,因为我们有自定义的插入/删除动画、滚动和视差效果等


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

  • 将这些分离器安装在单元内部的右侧
  • 使用带有
    最小行间距的纯黑色背景,这样我们可以在单元格之间的空格中看到背景
  • 使用自定义布局并将此分隔符用作装饰
前两个变体被拒绝,因为意识形态不一致、自定义动画和内容低于集合。此外,我已经有一个自定义布局

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

- (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];
}
--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
子类中,对于
layouttributesforementsinrect
中的装饰,我们应该返回
uicollectionviewlayouttributes

- (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-- 对于visible rect,我们应该返回可见的索引路径

- (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;
}
--6-- 有时,我发现这个解决方案会在装饰外观上出现视觉问题,这是通过实现
initiallayouttributesforappearingdecorrationelementofkind
解决的

- (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;
}
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath {
    UICollectionViewLayoutAttributes *layoutAttributes =  [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath];
    return layoutAttributes;
}

就这些。没有太多代码,但做得很好。

Anton的建议非常好,但我认为FlowLayout子类中的实现可以更简单。由于-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect的超级实现已返回单元格的布局属性,包括其框架和索引XPath,因此您有足够的信息来计算分隔符的框架,只需重写此方法并内省单元格布局属性:

- (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
            //e.g. 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,他们都帮了我-我接受了您的帮助,制作了一个拖放解决方案,作为
UICollectionView
上的一个类别,我想分享一下结果:

UICollectionView+分隔符.h

#import <UIKit/UIKit.h>

@interface UICollectionView (Separators)

@property (nonatomic) BOOL sep_useCellSeparators;
@property (nonatomic, strong) UIColor *sep_separatorColor;

@end
几点注意:

  • 可以使用
    collectionView:layout:minimumlinespaccingforsectionatindex:
    来确定每个部分的分隔符高度/宽度,如果未实施,则返回到
    minimumLineSpacing
  • 用于处理水平或垂直滚动方向

希望对您有所帮助

这是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);
    }
}
[寄存器(名称(FLCollectionSeparator))]
公共类FLCollectionSeparator:UICollectionReusableView
{
公共FLCollectionSeparator(CGRect框架):基础(框架)
{
this.BackgroundColor=UIColor.Black;
}
公共FLCollectionSeparator(IntPtr句柄):基础(句柄)
{
this.BackgroundColor=UIColor.Black;
}
公共覆盖无效ApplyLayoutAttributes(UICollectionViewLayoutAttributes layoutAttributes)
{
this.Frame=layouttributes.Frame;
}
}
[寄存器(名称(UILinedSpacedViewFlowLayout))]
公共类UILinedSpacedViewFlowLayout:UICollectionViewFlowLayout
{
public const string SeparatorAttribute=“Separator”;
私有静态只读NSString NSSeparatorAttribute=新NSString(SeparatorAttribute);
public UILinedSpacedViewFlowLayout():base(){this.InternalInit();}
公共UILinedSpacedViewFlowLayout(NSCoder coder):基(coder){this.InternalInit();}
受保护的UILinedSpacedViewFlowLayout(NSObjectFlag t):基(t){this.InternalInit();}
私有void InternalInit()
{
此.RegisterClassForDecorationView(typeof(FLCollectionSeparator),NSSeparatorAttribute);
}
公共覆盖UICollectionViewLayoutAttributes[]LayoutAttributesForElementsInRect(CGRect rect)
{
返回LayoutAttributesForElementsInRect_internal(rect).ToArray();
}
私有IEnumerable布局属性ForElementsInRect\u内部(cRect rect)
{
foreach(base.layouttributesforelementsinrect(rect))中的var baseDecorationAttr)
{
收益率回报率;
}
foreach(这个.IndexPathsOfSeparatorsInRect(rect))中的var indexPath)
{
yield返回this.layouttributesfordecorationview(NSSeparatorAttribute,indexPath);
}
}
private IEnumerable IndexPathsOfSeparatorsInRect(cRect 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);
对于(int i=Math.Max(firstCellIndexToShow,0);i快速求解Swift

1.创建CustomFlowLayout.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.设置自定义流

在界面生成器中,选择UICollectionViewFlow并设置新的类名
CustomFlowLayout

3.更改分隔符颜色

在分离器视图中,您可以在
init

4.更改分离器的高度

你可以用两种不同的方法来做

  • 在故事板中。更改线的属性
    Min Spacing

  • 在代码中。设置
    最小行间距的值

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

在@SlavikVoloshyn answer中,以编程方式使用本节:

override func awakeFromNib() {
    super.awakeFromNib()
    register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView)
}
需要更改为:

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

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

如果在前几行之外还有行,是否有方法防止该行显示。似乎不管是否有conten,都会生成这些行
override init() {
    super.init()
    register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView)
    minimumLineSpacing = 2
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}