ios UICollectionView 自定义行分隔符
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28691408/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView custom line separators
提问by Anton Gaenko
I wanna making 2pt black separatorsin 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.
我想使二点黑色隔板在UICollectionView
为我们的新的应用程序。我们的应用程序截图如下。我们不能使用UITableView
,因为我们有自定义的插入/删除动画、滚动和视差效果等等。
回答by Anton Gaenko
I started with three ideas how to make it:
我从三个想法开始:
- implement these separators right inside the cells
- use solid black backgroundwith
minimumLineSpacing
, thus we will see background in spaces between cells - use custom layoutand implement this separators as decorations
- 在单元格内部实施这些分隔符
- 使用纯黑色的背景与
minimumLineSpacing
,因此我们将看到背景细胞之间的空间 - 使用自定义布局并将此分隔符实现为装饰
First 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
.
我将使用UICollectionViewFlowLayout
.
--1--
--1--
Implement custom UICollectionReusableView
subclass.
实现自定义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--
--2--
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;
--3--
--3--
In custom UICollectionViewFlowLayout
subclass we should return UICollectionViewLayoutAttributes
for decorations from layoutAttributesForElementsInRect
.
在自定义UICollectionViewFlowLayout
子类中,我们应该UICollectionViewLayoutAttributes
从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--
--4--
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;
}
--5--
--5--
Also we should implement layoutAttributesForDecorationViewOfKind
.
我们也应该实现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--
--6--
Sometimes I found that this solution gives visual glitches with decorations appearance, which was fixed with implementing initialLayoutAttributesForAppearingDecorationElementOfKind
.
有时我发现这个解决方案会在装饰外观上产生视觉故障,这是通过实现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.
就这样。代码不多,但做得对。
回答by Slavik Voloshyn
Quick solution in Swift
Swift 中的快速解决方案
1. Create CustomFlowLayout.swift file and paste next code
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. Setup custom flow
2. 设置自定义流程
In the interface builder select your UICollectionViewFlow and set our new class name CustomFlowLayout
在界面构建器中选择您的 UICollectionViewFlow 并设置我们的新类名 CustomFlowLayout
3. Change a separator color
3.更改分隔符颜色
In SeparatorView you can change the color of separator in init
在 SeparatorView 中,您可以更改分隔符的颜色 init
4. Change a height of the separator
4.更改分隔符的高度
You can do it in two different ways
你可以用两种不同的方式来做
- In the storyboboard. Change a property
Min Spacing for Lines
- 在故事板中。更改属性
Min Spacing for Lines
OR
或者
In the code. Set value for
minimumLineSpacing
override func awakeFromNib() { super.awakeFromNib() register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView) minimumLineSpacing = 2 }
在代码中。设置值
minimumLineSpacing
override func awakeFromNib() { super.awakeFromNib() register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView) minimumLineSpacing = 2 }
回答by Werner Altewischer
Excellent suggestion by Anton, but I think the implementation in the FlowLayout sub class can be even simpler. Because the super implementation of - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect already returns the layout attributes of the cells including their frame and indexPath you have enough info to calculate the frames of the separators by overriding only this method and introspecting the cell layout attributes:
Anton 的建议很好,但我认为 FlowLayout 子类中的实现可以更简单。因为 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 的超级实现已经返回了单元格的布局属性,包括它们的框架和索引路径,所以你有足够的信息来通过仅覆盖此方法并内省单元格布局来计算分隔符的框架属性:
- (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];
}
回答by beebcon
Thanks, Anton & Werner, both helped me out - I've taken your help to make a drag & drop solution, as a category on UICollectionView
, thought I'd share the results:
谢谢,Anton 和 Werner,他们都帮助了我 - 我已经接受了你的帮助,制作了一个拖放解决方案,作为 上的一个类别UICollectionView
,我想我会分享结果:
UICollectionView+Separators.h
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
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
Using Objective-C runtime and some swizzling, cell separators can be added with a couple of lines to any existing UICollectionView
whose layout is/inherits from UICollectionViewFlowLayout
.
使用 Objective-C 运行时和一些swizzling,单元格分隔符可以通过几行添加到任何现有UICollectionView
的布局是/继承自UICollectionViewFlowLayout
.
Example Usage:
示例用法:
#import "UICollectionView+Separators.h"
...
self.collectionView.sep_useCellSeparators = YES;
self.collectionView.sep_separatorColor = [UIColor blackColor];
A couple notes:
一些注意事项:
- Separator height/width can be determined per section, using
collectionView:layout:minimumLineSpacingForSectionAtIndex:
, falling back onminimumLineSpacing
if not implemented - Built to handle horizontal or vertical scroll direction
- 分隔符的高度/宽度可以按部分确定,使用
collectionView:layout:minimumLineSpacingForSectionAtIndex:
,minimumLineSpacing
如果未实现则回退 - 用于处理水平或垂直滚动方向
Hope it helps
希望能帮助到你
回答by Pataphysicien
Here is the version from Anton Gaenko but implemented in C#, this could be usefull for Xamarin users :
这是 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);
}
}
回答by Saeid
In @SlavikVoloshyn answer for programmatically use this section:
在@SlavikVoloshyn 中以编程方式使用此部分的答案:
override func awakeFromNib() {
super.awakeFromNib()
register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView)
}
needs to change to :
需要更改为:
override init() {
super.init()
register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView)
minimumLineSpacing = 2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}