新しいアプリ用にUICollectionView
で2ptの黒いセパレータを作りたいです。アプリのスクリーンショットは以下にあります。カスタムの挿入/削除アニメーション、スクロール、視差効果などがあるため、UITableView
を使用できませんでした。
私は3つのアイデアから始めました。
minimumLineSpacing
を使用します。したがって、セル間のスペースに背景が表示されます。イデオロギーの不一致、カスタムアニメーション、コレクション以下のコンテンツがあるため、最初の2つのバリアントは拒否されました。また、私はすでにカスタムレイアウトを持っています。
UICollectionViewFlowLayout
のカスタムサブクラスを使用して手順を説明します。
カスタム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
カスタムデコレーションを使用するには、レイアウトと言います。また、セル間の行間隔を作成します。
UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*) self.newsCollection.collectionViewLayout;
[layout registerClass:[FLCollectionSeparator class] forDecorationViewOfKind:@"Separator"];
layout.minimumLineSpacing = 2;
カスタム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];
}
表示される四角形の場合は、表示される装飾インデックスパスを返す必要があります。
- (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;
}
また、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;
}
時々、このソリューションが装飾の外観を伴う視覚的なグリッチを与えることがわかりましたが、これはinitialLayoutAttributesForAppearingDecorationElementOfKind
の実装で修正されました。
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath {
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath];
return layoutAttributes;
}
それで全部です。コードは多すぎず、正しく行われました。
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
を設定します
。セパレーターの色を変更する
SeparatorViewでは、init
のセパレーターの色を変更できます
4。セパレーターの高さを変更する
あなたは2つの異なる方法でそれを行うことができます
Min Spacing for Lines
OR
コード内。 minimumLineSpacing
の値を設定
override func awakeFromNib() {
super.awakeFromNib()
register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView)
minimumLineSpacing = 2 }
Antonによる優れた提案ですが、FlowLayoutサブクラスでの実装はさらに簡単になると思います。 NS属性:
- (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];
}
おかげで、アントンとウェルナーの両方が私を助けてくれました-UICollectionView
のカテゴリとして、ドラッグアンドドロップソリューションを作成するためにあなたの助けを借りて、結果を共有すると思いました:
ICollectionView + Separators.h
#import <UIKit/UIKit.h>
@interface UICollectionView (Separators)
@property (nonatomic) BOOL sep_useCellSeparators;
@property (nonatomic, strong) UIColor *sep_separatorColor;
@end
ICollectionView + 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ランタイムと一部のswizzlingを使用すると、レイアウトがUICollectionView
である/継承する既存のUICollectionViewFlowLayout
に数行でセルセパレーターを追加できます。
使用例:
#import "UICollectionView+Separators.h"
...
self.collectionView.sep_useCellSeparators = YES;
self.collectionView.sep_separatorColor = [UIColor blackColor];
いくつかのメモ:
collectionView:layout:minimumLineSpacingForSectionAtIndex:
を使用し、実装されていない場合は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);
}
}
@SlavikVoloshynでは、このセクションをプログラムで使用するための答え:
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")
}