UICollectionViewLayoutAttributes是UICollectionView的重要组成部分,本文从其基本定义、如何使用以及使用场景几方面来简单介绍。文末以自定义UICollectionView各个Section的背景色的示例来展示UICollectionViewLayoutAttributes的应用。

UICollectionViewLayoutAttributes概览

UICollectionViewLayoutAttributes的官方解释:

A layout object that manages the layout-related attributes for a given item in a collection view.Layout objects create instances of this class when asked to do so by the collection view. In turn, the collection view uses the layout information to position cells and supplementary views inside its bounds.

UICollectionViewLayoutAttributes是管理collection view中指定元素的布局属性的对象。collection view使用该对象中的布局属性来控制cells和supplementary views的显示位置。

下面是UICollectionViewLayoutAttributes类的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NS_CLASS_AVAILABLE_IOS(6_0) @interface UICollectionViewLayoutAttributes : NSObject <NSCopying, UIDynamicItem>

@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, strong) NSIndexPath *indexPath;

@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; // nil when representedElementCategory is UICollectionElementCategoryCell

+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;

@end

其中UICollectionElementCategory的定义如下:

1
2
3
4
5
typedef NS_ENUM(NSUInteger, UICollectionElementCategory) {
UICollectionElementCategoryCell,
UICollectionElementCategorySupplementaryView,
UICollectionElementCategoryDecorationView
};

Because layout attribute objects may be copied by the collection view, it conforms to the NSCopying protocol. It is very important that we also conform to this protocol and implement copyWithZone:. Otherwise, our property will always be zero (as guaranteed by the compiler).

由于layout attributes对象可能会被collection view复制,因此layout attributes对象应该遵循NSCoping协议,并实现copyWithZone:方法,否则我们获取的自定义属性会一直是空值。

If you subclass and implement any custom layout attributes, you must also override the inherited isEqual: method to compare the values of your properties. In iOS 7 and later, the collection view does not apply layout attributes if those attributes have not changed. It determines whether the attributes have changed by comparing the old and new attribute objects using the isEqual: method. Because the default implementation of this method checks only the existing properties of this class, you must implement your own version of the method to compare any additional properties. If your custom properties are all equal, call super and return the resulting value at the end of your implementation.

如果继承了UICollectionViewLayoutAttributes并且添加了任何自定义的layout attributes,也必须实现isEqual:方法来比较自定义属性。在iOS7(包括iOS7)以后,如果UICollectionViewLayoutAttributes 的属性值没有改变,collection view不会应用layout attributes,这些layout attributes的是否改变由isEqual:的返回值来决定。在重写isEqual:时,除了需要处理自定义属性外,还需要注意父类方法的调用。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** subclass must conforms to the NSCopying protocol */
- (id)copyWithZone:(NSZone *)zone {

CLSectionColorLayoutAttributes *layoutAttributes = [super copyWithZone:zone];
layoutAttributes.sectionColor = self.sectionColor;
return layoutAttributes;
}

/** In iOS 7 and later, the collection view does not apply layout attributes if
those attributes have not changed. It determines whether the attributes have changed
by comparing the old and new attribute objects using the isEqual: method. */
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if ([object class] == [self class]) {
return [super isEqual:object] && (self.sectionColor == [object sectionColor]);
}
return NO;
}

Subclassing Notes

首先,在UICollectionViewLayoutAttributes的子类中重写copyWithZone:方法和isEqual:方法。其原因已在上一节讲到。

接下来需要告诉collection view使用自定义的类而不是系统的UICollectionViewLayoutAttributes类,需要在自定义的CLCollectionViewFlowLayout中重写类方法+(Class)layoutAttributesClass,如下所示:

1
2
3
+ (Class)layoutAttributesClass {
return [CLSectionColorLayoutAttributes class];
}

为了与系统的配置属性使用保持一致,我们可以在CLCollectionViewFlowLayout头文件中暴露自定义属性,以便对该属性的统一设置。如下:

1
2
3
4
5
6
7
8
.h
@property (nonatomic, strong) UIColor *sectionColor;

.m
- (void)setSectionColor:(UIColor *)sectionColor {
_sectionColor = sectionColor;
[self invalidateLayout];
}

同时,为了达到与系统的UICollectionViewDelegateFlowLayout使用达到一致,可以支持自定义属性针对不同indexPath的设置,我们声明protocol以供业务层调用,如下:

1
2
3
4
5
6
7
@protocol CLCollectionViewDelegateFlowLayout <UICollectionViewDelegateFlowLayout>

@optional
- (UIColor *)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
colorForSectionAtIndex:(NSInteger)section;
@end

这里需要注意,我们自定义的协议遵循了UICollectionViewDelegateFlowLayout协议,且无需在CLCollectionViewFlowLayout类中声明一个遵循该协议的delegate对象,该协议的使用与UICollectionViewDelegateFlowLayout一致。layout的实现文件中优先使用该协议返回的值,否则使用属性中传入的值。如下:

1
2
3
4
5
6
7
8
9
- (UIColor *)sectionColorAtSection:(NSInteger)section {
UIColor *sectionColor = self.sectionColor;
//if implemented collectionView:layout:colorForSectionAtIndex: use the return value
if ([self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:colorForSectionAtIndex:)]) {
id<CLCollectionViewDelegateFlowLayout> temp = (id<CLCollectionViewDelegateFlowLayout>)self.collectionView.delegate;
sectionColor = [temp collectionView:self.collectionView layout:self colorForSectionAtIndex:section];
}
return sectionColor;
}

sectionColorAtSection:方法会在layoutAttributesForDecorationViewOfKind:atIndexPath:中调用。

至此,自定义属性传递给了layout,但是是怎样应用到具体的Cell或者SupplementaryView上的呢?继承自UICollectionReusableView或者UICollectionViewCell的控制,包含如下方法:

1
2
3
4
5
// Classes that want to support custom layout attributes specific to a given UICollectionViewLayout subclass can apply them here.
// This allows the view to work in conjunction with a layout class that returns a custom subclass of UICollectionViewLayoutAttributes from -layoutAttributesForItem: or the corresponding layoutAttributesForHeader/Footer methods.
// -applyLayoutAttributes: is then called after the view is added to the collection view and just before the view is returned from the reuse queue.
// Note that -applyLayoutAttributes: is only called when attributes change, as defined by -isEqual:.
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;

例如如下的实现:

1
2
3
4
5
6
7
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
[super applyLayoutAttributes:layoutAttributes];
if ([layoutAttributes isKindOfClass:[CLSectionColorLayoutAttributes class]]) {
CLSectionColorLayoutAttributes *attributes = (CLSectionColorLayoutAttributes *)layoutAttributes;
self.backgroundColor = attributes.sectionColor;
}
}

This method belongs to UICollectionReusableView because layout attributes are applicable to cells, supplementary views, and decoration views. First, you must call super’s implementation. Next, it checks to ensure that the layout attributes are an instance of our custom subclass before casting the pointer.

注意:该方法声明在UICollectionReusableView类中,适用于cells、supplementary view、decoration views。首先你必须调用父类的实现,然后检查layoutAttributes是否自定义类的实例,来决定是否进行指针的强制转换。

自定义UICollectionView各个Section的背景色

前面讲解了UICollectionViewLayoutAttributes的基本使用,使用的示例代码是为了能够达到自定义UICollectionView的各个Section的背景色。你可能对这个需求有疑惑,在UICollectionView中,每个Section中可以定义sectionInset、minimumLineSpacing、minimumInteritemSpacing属性值,而这些属性值定义的空白区域的背景色是与UICollectionView的背景色保持一致,当我们的需求中需要定义与UICollectionView的背景色不一致时,或者各个Section定义的背景色也各有区别时,

为了达到此目的,方案是在各个section中添加decoration view,然后设置decoration view的颜色就是各个section的背景色,具体的代码见 github中下载,如有错误之处,欢迎指正。

需要重点提出的是,如果你想在删除cell或者section的时候动态添加或者删除decoration view,你可能需要花点功夫来使cell的添加或者删除动画与decoration view的添加与删除动画保持同步。

footerReferenceSize and headerReferenceSize During layout, only the size that corresponds to the appropriate scrolling direction is used. For example, for the vertical scrolling direction, the layout object uses the height value returned by your method. (In that instance, the width of the header would be set to the width of the collection view.) If the size in the appropriate scrolling dimension is 0, no header is added.

使用场景

UICollectionViewLayoutAttributes使用场景,个人总结如下: 1.与DataSource无关,但想控制UIReusableView或者UICollectionViewCell的显示属性,例如在ViewController中配置UIImageView的显示模式。 2.UICollectionviewCell的某一属性需要跟随UICollectionViewLayout的改变而改变,例如CoverFlow类型的展示。


目前已转行教育行业,欢迎加微信交流:CaryaLiu