UICollectionViewLayoutAttributes初探

综合技术 2017-12-29

UICollectionViewLayoutAttributes是UICollectionView的重要组成部分,本文从其基本定义、如何使用以及使用场景几方面来简单介绍。

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类的定义:

NS_CLASS_AVAILABLE_IOS(6_0) @interface UICollectionViewLayoutAttributes : NSObject 

@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的定义如下:

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: 时,除了需要处理自定义属性外,还需要注意父类方法的调用。如下所示:

/** 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 ,如下所示:

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

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

.h
@property (nonatomic, strong) UIColor *sectionColor;

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

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

@protocol CLCollectionViewDelegateFlowLayout 

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

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

- (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 temp = (id)self.collectionView.delegate;
        sectionColor = [temp collectionView:self.collectionView layout:self colorForSectionAtIndex:section];
    }
    return sectionColor;
}

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

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

// 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;

例如如下的实现:

- (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类型的展示。

如果觉得本文对你有帮助,就请用微信随意打赏我吧^_^

责编内容by:CaryaLiu's Blog (源链)。感谢您的支持!

您可能感兴趣的

Markdown语法简单使用 简介 Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。 Markdown具有一系列衍生版本,用于扩展Markdown的功能...
OpenGL ES on iOS — 坐标系统与矩阵转换... 本文记录我记录我学习 坐标体系和矩阵转换的过程,加深学习便于后续查询,可能有些描述不够准确,或者内容不够充实,还请多多指正,共同学习 矩阵变换 我们将物体坐标进行一系列变换,达到自己期望的位置...
Leak in @property setter I have a custom UIView that I add as a subview in several places throughout my app. I send the view ...
How do I perform this delegate correctly&quest... I have a table view controller and a view controller. StackTableViewController - list ...
由class_getMethodImplementation函数引发的思考... 昨天逛简书的时候在一篇文章的评论下面get到一个挺有意思的问题,也没有见到楼主或其它简友给出合理的解释,细想之后遂对其产生了浓烈的兴趣。下面贴出这位简友所提的问题: + (void)load{ ...