美文网首页
iOS UICollectionView之自定义Layout

iOS UICollectionView之自定义Layout

作者: 点滴86 | 来源:发表于2016-12-16 15:28 被阅读377次

    UICollectionViewLayoutAttributes
    UICollectionViewLayoutAttributes是一个非常重要的类,属性列表如下

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

    UICollectionViewLayoutAttributes的实例中包含了诸如边框、中心点、大小、形状、边界、透明度、层次关系和是否隐藏等信息。当UICollectionView在获取布局时将针对每一个indexPath的部件(包括cell、SupplementaryView和DecorationView),向其上的UICollectionViewLayout实例询问该部件的布局信息,这个布局信息,就是以UICollectionViewLayoutAttributes的实例方式给出的。
    自定义UICollectionViewLayout
    UICollectionViewLayout的功能是为UICollectionView提供布局信息,不仅包括cell的布局信息,也包括SupplementaryView和DecorationView的布局信息。实现一个自定义的UICollectionViewLayout的常规做法是继承UICollectionViewLayout,然后重载下列方法

    // 返回collectionView的内容尺寸
    -(CGSize)collectionViewContentSize
    
    /**
     *  返回rect中的所有元素的布局属性
     *  返回的是包含UICollectionViewLayoutAttributes的数组
     *  UICollectionViewLayoutAttributes可以是cell、SupplementaryView或者DecorationView的信息通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes
     *  + (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath
     *  + (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath
     *  + (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath
     */
    -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
    
    // 返回对应于indexPath的位置的cell的布局属性
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
    
    // 返回对应于indexPath位置的SupplementaryView的布局属性,如果没有SupplementaryView可不重载
    - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
    
    //  返回对应于indexPath位置的DecorationView的布局属性,如果没有DecorationView可不重载
    - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
    
    //  当边界发生改变时,是否应该刷新布局。如果YES则在边界变化时,将重新计算需要的布局信息。
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    

    初始化一个UICollectionViewLayout实例后,首先-(void)prepareLayout将被调用,之后-(CGSize)collectionViewContentSize被调用,以确定collectionView应该占据的尺寸。注意这里的尺寸不是指可是部分的尺寸,应该是所有内容占据的尺寸。接下来-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect被调用,获取部件的布局信息。

    CircleLayout---官方示例
    参数声明

    @interface CircleLayout ()
    
    @property (nonatomic, assign) CGPoint center;
    
    @property (nonatomic, assign) CGFloat radius;
    
    @property (nonatomic, assign) NSInteger cellCount;
    
    // arrays to keep track of insert, delete index paths
    @property (nonatomic, strong) NSMutableArray *deleteIndexPaths;
    
    @property (nonatomic, strong) NSMutableArray *insertIndexPaths;
    
    @end
    

    首先布局准备中计算所需要用到的参数

    -(void)prepareLayout
    {
        [super prepareLayout];
        
        CGSize size = self.collectionView.frame.size;
        _cellCount = [[self collectionView] numberOfItemsInSection:0];
        _center = CGPointMake(size.width / 2.0, size.height / 2.0);
        _radius = MIN(size.width, size.height) / 2.5;
    }
    

    然后按照UICollectionViewLayout子类的要求重载所需要的方法

    // 返回collectionView的内容尺寸
    -(CGSize)collectionViewContentSize
    {
        return [self collectionView].frame.size;
    }
    
    // 返回对应于indexPath的位置的cell的布局属性
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
    {
        UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
        attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
        attributes.center = CGPointMake(_center.x + _radius * cosf(2 * path.item * M_PI / _cellCount),
                                        _center.y + _radius * sinf(2 * path.item * M_PI / _cellCount));
        return attributes;
    }
    
    -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
    {
        NSMutableArray* attributes = [NSMutableArray array];
        for (NSInteger i=0 ; i < self.cellCount; i++) {
            NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];
            [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
        }
        return attributes;
    }
    
    

    增加删除cell的动画

    - (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
    {
        // Keep track of insert and delete index paths
        [super prepareForCollectionViewUpdates:updateItems];
        
        self.deleteIndexPaths = [NSMutableArray array];
        self.insertIndexPaths = [NSMutableArray array];
        
        for (UICollectionViewUpdateItem *update in updateItems)
        {
            if (update.updateAction == UICollectionUpdateActionDelete)
            {
                [self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
            }
            else if (update.updateAction == UICollectionUpdateActionInsert)
            {
                [self.insertIndexPaths addObject:update.indexPathAfterUpdate];
            }
        }
    }
    
    - (void)finalizeCollectionViewUpdates
    {
        [super finalizeCollectionViewUpdates];
        // release the insert and delete index paths
        self.deleteIndexPaths = nil;
        self.insertIndexPaths = nil;
    }
    
    // Note: name of method changed
    // Also this gets called for all visible cells (not just the inserted ones) and
    // even gets called when deleting cells!
    - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    {
        // Must call super
        UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
        
        if ([self.insertIndexPaths containsObject:itemIndexPath])
        {
            // only change attributes on inserted cells
            if (!attributes)
                attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
            
            // Configure attributes ...
            attributes.alpha = 0.0;
            attributes.center = CGPointMake(_center.x, _center.y);
        }
        
        return attributes;
    }
    
    // Note: name of method changed
    // Also this gets called for all visible cells (not just the deleted ones) and
    // even gets called when inserting cells!
    - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    {
        // So far, calling super hasn't been strictly necessary here, but leaving it in
        // for good measure
        UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
        
        if ([self.deleteIndexPaths containsObject:itemIndexPath])
        {
            // only change attributes on deleted cells
            if (!attributes)
                attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
            
            // Configure attributes ...
            attributes.alpha = 0.0;
            attributes.center = CGPointMake(_center.x, _center.y);
            attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
        }
        
        return attributes;
    }
    

    在ViewController中为collectionView增加手势识别

    UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
        [self.collectionView addGestureRecognizer:tapRecognizer];
    

    手势响应方法

    #pragma mark - event response
    - (void)handleTapGesture:(UITapGestureRecognizer *)sender {
        
        if (sender.state == UIGestureRecognizerStateEnded)
        {
            CGPoint initialPinchPoint = [sender locationInView:self.collectionView];
            NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:initialPinchPoint];
            if (tappedCellPath!=nil)
            {
                self.cellCount = self.cellCount - 1;
                [self.collectionView performBatchUpdates:^{
                    [self.collectionView deleteItemsAtIndexPaths:@[tappedCellPath]];
                    
                } completion:nil];
            }
            else
            {
                self.cellCount = self.cellCount + 1;
                [self.collectionView performBatchUpdates:^{
                    [self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
                } completion:nil];
            }
        }
    }
    

    CircleLayout 中cell都分布在圆周上,点击cell的话会将其从collectionView中移除,点击空白处会加入一个cell,加入和移除cell都会有动画效果.
    效果图如下

    CircleLayout.png

    相关文章

      网友评论

          本文标题:iOS UICollectionView之自定义Layout

          本文链接:https://www.haomeiwen.com/subject/lidnmttx.html