继承UICollectionViewFlowLayout

继承UICollectionViewFlowLayout

继承UICollectionViewFlowLayout可以做什么呢?

  • 修改布局的属性attribute
  • 在layout中添加decoration view
  • 添加新的supplymentary view
  • 扩展UICollectionViewLayoutAttributes,添加新的属性
  • 添加手势支持
  • 自定义insert、update、delete等动画

iOS UICollectionView The Complete Guide一书中,由cell没有对齐,而自定义UICollectionViewFlowLayout,如下图所示:

继承UICollectionViewFlowLayout_第1张图片

而理想的效果是希望cell能对齐:

继承UICollectionViewFlowLayout_第2张图片

为实现对齐效果,如下的2个方法子类要重写,这些方法会在collection view布局cell、supplementary view、decoration view时调用

1.layoutAttributesForElementsInRect:

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

方法说明:

Subclasses must override this method and use it to return layout information for all items whose view intersects the specified rectangle. Your implementation should return attributes for all visual elements, including cells, supplementary views, and decoration views.
When creating the layout attributes, always create an attributes object that represents the correct element type (cell, supplementary, or decoration). The collection view differentiates between attributes for each type and uses that information to make decisions about which views to create and how to manage them.
该方法对所有的元素类型有效,并不仅仅是cell

2.layoutAttributesForItemAtIndexPath:

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

方法说明:

Subclasses must override this method and use it to return layout information for items in the collection view. You use this method to provide layout information only for items that have a corresponding cell. Do not use it for supplementary views or decoration views.
对collection view中item有效,为cell提供布局信息
对supplementary view or decoration view 不要使用该方法

主要代码如下:

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes *attributes in attributesArray)
    {
        [self applyLayoutAttributes:attributes];
    }    
    return attributesArray;
}

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];

    [self applyLayoutAttributes:attributes];

    return attributes;
}

//自定义方法
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes
{
    // representedElementKind 为 nil, 表示这是一个cell,而不是 header 或者 decoration view
    if (attributes.representedElementKind == nil)
    {
        CGFloat width = [self collectionViewContentSize].width;
        CGFloat leftMargin = [self sectionInset].left;
        CGFloat rightMargin = [self sectionInset].right;

        NSUInteger itemsInSection = [[self collectionView] numberOfItemsInSection:attributes.indexPath.section];
        CGFloat firstXPosition = (width - (leftMargin + rightMargin)) / (2 * itemsInSection);
        CGFloat xPosition = firstXPosition + (2*firstXPosition*attributes.indexPath.item);

        attributes.center = CGPointMake(leftMargin + xPosition, attributes.center.y);
        attributes.frame = CGRectIntegral(attributes.frame);
    }
}

修改后的效果如下:

继承UICollectionViewFlowLayout_第3张图片

添加decoration view

decoration view不是数据驱动的(data driven),所以你无法在view controller中来添加decoration view

decoration view相关的代码,要写在自定义的布局类和自定义的UICollectionReusableView

自定义UICollectionReusableView

如下的AFDecorationView,继承自UICollectionReusableView,仅仅添加一个图片控件:

@implementation AFDecorationView
{
    UIImageView *binderImageView;
}

- (id)initWithFrame:(CGRect)frame
{
    if (!(self = [super initWithFrame:frame])) return nil;

    binderImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"binder"]];
    binderImageView.frame = CGRectMake(10, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
    binderImageView.contentMode = UIViewContentModeLeft;
    binderImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    [self addSubview:binderImageView];

    return self;
}


@end

在自定义的布局中处理

1.修改layoutAttributesForElementsInRect:方法

NSString * const AFCollectionViewFlowLayoutBackgroundDecoration = @"DecorationIdentifier";

......

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect];

    NSMutableArray *newAttributesArray = [NSMutableArray array];

    for (UICollectionViewLayoutAttributes *attributes in attributesArray)
    {
        [self applyLayoutAttributes:attributes];

        if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView)
        {

            UICollectionViewLayoutAttributes *newAttributes = [self layoutAttributesForDecorationViewOfKind:AFCollectionViewFlowLayoutBackgroundDecoration atIndexPath:attributes.indexPath];

            [newAttributesArray addObject:newAttributes];
        }
    }

    attributesArray = [attributesArray arrayByAddingObjectsFromArray:newAttributesArray];

    return attributesArray;
}

说明:在我们的例子中,header与decoration view是一一对应的,所以在if语句中,判断类型如果是UICollectionElementCategorySupplementaryView,就新增一个布局属性

2.实现-layoutAttributesForDecorationViewOfKind:atIndexPath:方法

该方法:

Returns the layout attributes for the specified decoration view.
If your layout object defines any decoration views, you must override this method and use it to return layout information for those views.
返回decoration view的布局属性
如果布局对象中定义了decoration view,必须重写这个方法

-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind
                                                                 atIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];

    //自定义的decoration view
    if ([decorationViewKind isEqualToString:AFCollectionViewFlowLayoutBackgroundDecoration])
    {
        UICollectionViewLayoutAttributes *tallestCellAttributes;
        //section有多少个cell
        NSInteger numberOfCellsInSection = [self.collectionView numberOfItemsInSection:indexPath.section];

        for (NSInteger i = 0; i < numberOfCellsInSection; i++)
        {
            NSIndexPath *cellIndexPath = [NSIndexPath indexPathForItem:i inSection:indexPath.section];

            //cell的attribute
            UICollectionViewLayoutAttributes *cellAttribtes = [self layoutAttributesForItemAtIndexPath:cellIndexPath];

            if (CGRectGetHeight(cellAttribtes.frame) > CGRectGetHeight(tallestCellAttributes.frame))
            {
                //最高的cell
                tallestCellAttributes = cellAttribtes;
            }
        }

        //CGFloat decorationViewHeight = CGRectGetHeight(tallestCellAttributes.frame) + self.headerReferenceSize.height;

        //decoration view的高度
        CGFloat decorationViewHeight = CGRectGetHeight(tallestCellAttributes.frame) + self.sectionInset.top + self.sectionInset.bottom;

        layoutAttributes.size = CGSizeMake([self collectionViewContentSize].width, decorationViewHeight);
        layoutAttributes.center = CGPointMake([self collectionViewContentSize].width / 2.0f, tallestCellAttributes.center.y);
        layoutAttributes.frame = CGRectIntegral(layoutAttributes.frame);
        // 放在cell的后面
        layoutAttributes.zIndex = -1;
    }

    return layoutAttributes;
}

效果如下:

继承UICollectionViewFlowLayout_第4张图片

支持动画

动画相关方法:

//在一个新的item被添加到collection view 或者 item被更新时调用
//可使用该方法,为item在开始动画之前提供布局属性信息
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath

//该方法使用同initialLayoutAttributesForAppearingItemAtIndexPath

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath


//该方法使用同initialLayoutAttributesForAppearingItemAtIndexPath

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath

上面的方法在 prepareForCollectionViewUpdates:后调用, 在finalizeCollectionViewUpdates 之前被调用

//即将移除的item的布局信息
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath;

//类似finalLayoutAttributesForDisappearingItemAtIndexPath
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath;

//类似finalLayoutAttributesForDisappearingItemAtIndexPath
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath;

上面的方法在 prepareForCollectionViewUpdates:后调用, 在finalizeCollectionViewUpdates 之前被调用

//做一些和布局相关的准备工作
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems;

//做一些和布局相关的准备工作
- (void)finalizeCollectionViewUpdates; // called inside an animation block after the update

例子如下,实现如下的方法:

-(void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
    [super prepareForCollectionViewUpdates:updateItems];

    [updateItems enumerateObjectsUsingBlock:^(UICollectionViewUpdateItem *updateItem, NSUInteger idx, BOOL *stop) {
        if (updateItem.updateAction == UICollectionUpdateActionInsert)
        {
            [insertedSectionSet addObject:@(updateItem.indexPathAfterUpdate.section)];
        }
    }];
}

-(void)finalizeCollectionViewUpdates
{
    [super finalizeCollectionViewUpdates];

    [insertedSectionSet removeAllObjects];
}

-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath
{
    //returning nil will cause a crossfade

    UICollectionViewLayoutAttributes *layoutAttributes;

    if ([elementKind isEqualToString:AFCollectionViewFlowLayoutBackgroundDecoration])
    {
        if ([insertedSectionSet containsObject:@(decorationIndexPath.section)])
        {
            layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath];
            layoutAttributes.alpha = 0.0f;
            layoutAttributes.transform3D = CATransform3DMakeTranslation(-CGRectGetWidth(layoutAttributes.frame), 0, 0);
        }
    }

    return layoutAttributes;
}

-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    //returning nil will cause a crossfade

    UICollectionViewLayoutAttributes *layoutAttributes;

    if ([insertedSectionSet containsObject:@(itemIndexPath.section)])
    {
        layoutAttributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
        layoutAttributes.transform3D = CATransform3DMakeTranslation([self collectionViewContentSize].width, 0, 0);
    }

    return layoutAttributes;
}

效果如下:

你可能感兴趣的:(继承UICollectionViewFlowLayout)