Je veux faire séparateurs noirs 2pt in UICollectionView
pour notre nouvelle application. Capture d'écran de notre application est ci-dessous. Nous ne pouvions pas utiliser UITableView
, car nous avons des animations d'insertion/suppression personnalisées, des effets de défilement et de parallaxe, etc.
J'ai commencé avec trois idées pour le faire:
minimumLineSpacing
, nous verrons donc le fond dans les espaces entre les cellulesLes deux premières variantes ont été rejetées à cause d'une incohérence idéologique, d'animations personnalisées et d'un contenu inférieur à la collection. De plus, j'ai déjà une mise en page personnalisée.
Je vais décrire les étapes avec une sous-classe personnalisée de UICollectionViewFlowLayout
.
Implémentez la sous-classe UICollectionReusableView
personnalisée.
@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
Dites mise en page pour utiliser des décorations personnalisées. Faites également un espacement entre les cellules.
UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*) self.newsCollection.collectionViewLayout;
[layout registerClass:[FLCollectionSeparator class] forDecorationViewOfKind:@"Separator"];
layout.minimumLineSpacing = 2;
Dans la sous-classe UICollectionViewFlowLayout
personnalisée, nous devrions renvoyer UICollectionViewLayoutAttributes
pour les décorations de 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];
}
Pour rect visible, nous devrions retourner les chemins d'index des décorations visibles.
- (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;
}
Nous devrions aussi implémenter 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;
}
Parfois, je trouvais que cette solution donnait des problèmes visuels avec l’apparence de décorations, ce qui a été corrigé avec l’application de initialLayoutAttributesForAppearingDecorationElementOfKind
.
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath {
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath];
return layoutAttributes;
}
C'est tout. Pas trop de code mais bien fait.
Excellente suggestion de Anton, mais je pense que la mise en œuvre dans la sous-classe FlowLayout peut être encore plus simple. Étant donné que la super-implémentation de - (NSArray *) layoutAttributesForElementsInRect: (CGRect) rect renvoie déjà les attributs de présentation des cellules, y compris leur cadre et indexPath, vous avez suffisamment d'informations pour calculer les cadres des séparateurs en ne surchargeant que cette méthode et en analysant la structure de cellule. les attributs:
- (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];
}
Solution rapide dans Swift
1. Créez le fichier CustomFlowLayout.Swift et collez le code suivant
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. Configurer le flux personnalisé
Dans le générateur d'interface, sélectionnez votre UICollectionViewFlow et définissez notre nouveau nom de classe CustomFlowLayout
3. Changer une couleur de séparateur
Dans SeparatorView, vous pouvez changer la couleur du séparateur dans init
4. Modifier la hauteur du séparateur
Vous pouvez le faire de deux manières différentes
Min Spacing for Lines
OR
Dans le code. Définir la valeur pour minimumLineSpacing
override func awakeFromNib() {
super.awakeFromNib()
register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationView)
minimumLineSpacing = 2 }
Merci, Anton & Werner, tous les deux m'ont aidé - j'ai pris votre aide pour créer une solution de glisser-déposer, en tant que catégorie sur UICollectionView
, pensant partager les résultats:
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
En utilisant le runtime Objective-C et certains swizzling , des séparateurs de cellules peuvent être ajoutés avec quelques lignes à toute UICollectionView
existante dont la présentation est/hérite de UICollectionViewFlowLayout
.
Exemple d'utilisation:
#import "UICollectionView+Separators.h"
...
self.collectionView.sep_useCellSeparators = YES;
self.collectionView.sep_separatorColor = [UIColor blackColor];
Quelques notes:
collectionView:layout:minimumLineSpacingForSectionAtIndex:
, retombant sur minimumLineSpacing
si non implémentéJ'espère que ça aide
Voici la version de Anton Gaenko mais implémentée en C #, cela pourrait être utile pour les utilisateurs de 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);
}
}