UICollectionView(摘要版)

参考:
自定义布局和自定义流水布局

换句话说,UITableView的布局是UICollectionView的flow layout布局的一种特殊情况,类比于同矩形与正方形的关系


collectionView注册和重用时的identify不一致,会闪退报这个错:
reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier firstCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

2.collectionview 刚开始的实例的时候一定要给尺寸,否则会闪


下面重点说说,除了系统默认的一般的流水布局之外的自定义布局:

自定义布局分两种情况: 1.继承UICollectionViewFlowLayout。 2.直接继承其上一层的UICollectionViewLayout。

1.先来说说第一种继承UICollectionViewFlowLayout的情况。

关键是两大基础方法,经过调试,发现其触发顺序依次为:
1.1,

/**
 *  这个方法的特点是:当collectionView的显示范围发生改变的时候,判断是否需要重新刷新布局
 *  一旦重新刷新布局,就重新调用下面的方法:
 1.prepareLayout
 2.layoutAttibutesForElementsInrect:
 *
 *  @return 默认是NO
 */
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds//1
{
    return YES;
}

1.2

- (void)prepareLayout//2
{
  //在这个方法中做一些初始化操作
    [super prepareLayout];
      //collectionview貌似不重写布局改不了水平滑动
        // 水平滚动
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    // 设置内边距
//    CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
//    self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}

1.3,因为第三步有很多方法供自定义,这仅是举个例子来聊一下,(例子:根据当前item是否在collectionView中点来确定其缩放

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect//3
{
    // 获得super已经计算好的布局属性
    NSArray *elementsArray = [super layoutAttributesForElementsInRect:rect];
    // 计算collectionView最中心点的x值
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
   //在原有布局属性的基础上,进行微调
    for (int i = 0;i

首先要吐槽的是:
为什么是处理数组(elementsArray)?

原因:UICollectionViewLayoutAttributes

  1. 它是描述布局属性的
  2. 一个item对应一个UICollectionViewLayoutAttributes对象
  3. UICollectionViewLayoutAttributes对象决定了cell的展示样式(frame)说白了就是决定你的item摆在哪里,怎么去摆

换言之:
因为每个item对应着一个UICollectionViewLayoutAttributes对象,所以会有很多该对象,所以遍历数组来处理,就不足为奇了。

吐槽点2:
计算collectionView的最中心点的x值为什么要加上偏移值(contentOffset)呢?(ps:调试发现,水平方向上,向左滑为正,右滑为负)

总的来说,是被迫加的!
因为要拿item的centerX,但是item的UICollectionViewLayoutAttributes对象坐标原点是以内容的contentsize的原点(contentsize最左上角的那个)为原点的,所以如果要算该cell与collectionview的中点的差值时,collectionView的中点被迫加上偏移值。

另外,值得一提的是

在算比例的过程中,0是一个很特殊位置的,一般用来处理目标位置


以下是布局成圆的例子:


- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}


- (void)prepareLayout
{
    [super prepareLayout];
    
    [self.attrsArray removeAllObjects];
    
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i < count; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

/**
 * 这个方法需要返回indexPath位置对应cell的布局属性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    CGFloat radius = 70;
    // 圆心的位置
    CGFloat oX = self.collectionView.frame.size.width * 0.5;
    CGFloat oY = self.collectionView.frame.size.height * 0.5;
    
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    attrs.size = CGSizeMake(50, 50);
    if (count == 1) {
        attrs.center = CGPointMake(oX, oY);
    } else {
        CGFloat angle = (2 * M_PI / count) * indexPath.item;
        CGFloat centerX = oX + radius * sin(angle);
        CGFloat centerY = oY + radius * cos(angle);
        attrs.center = CGPointMake(centerX, centerY);
    }
    
    return attrs;
}

二。2.直接UICollectionViewLayout(非流水布局的根布局)

2.1使用的场景:

一开始,itemSize不一样大(流水布局的ItemSize貌似一开始一样大的,后面调的不算)

2.2 使用的注意:

1.一旦你重写了layoutAttributesForElementsInRect这个方法,就意味着所有东西你得自己写了,你的Attributes对象得自己创建了,因为它的父类不会帮你创建
2.一旦你继承自CollectionViewLayout,意味着你这个collectionViewContentSize都得告诉它了,这个是得你自己去算的(这个跟用原始的scrollview一样的)
3.如果你是希望一口气把所有东西算完,不希望它在滚动过程中再算,你可以在prepareLayout方法里面先算清楚,算完后尽管它传的矩形框都不一样。

2.3 使用的举例:

- (void)prepareLayout
{
    [super prepareLayout];
    
    [self.attrsArray removeAllObjects];
    
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    
    for (int i = 0; i < count; i ++)
    {
        //取出单个布局的对象
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        
        //设置布局属性
        CGFloat width = self.collectionView.frame.size.width * 0.5;
        if (i == 0) {
            CGFloat height = width;
            CGFloat x = 0;
            CGFloat y = 0;
            attrs.frame = CGRectMake(x, y, width, height);
        } else if (i == 1){
            CGFloat height = width * 0.5;
            CGFloat x = width;
            CGFloat y = 0;
            attrs.frame = CGRectMake(x, y, width, height);
        } else if (i == 2){
            CGFloat height = width * 0.5;
            CGFloat x = width;
            CGFloat y = height;
            attrs.frame = CGRectMake(x, y, width, height);
        }else if (i == 3) {
            CGFloat height = width * 0.5;
            CGFloat x = 0;
            CGFloat y = width;
            attrs.frame = CGRectMake(x, y, width, height);
        } else if (i == 4) {
            CGFloat height = width * 0.5;
            CGFloat x = 0;
            CGFloat y = width + height;
            attrs.frame = CGRectMake(x, y, width, height);
        } else if (i == 5) {
            CGFloat height = width;
            CGFloat x = width;
            CGFloat y = width;
            attrs.frame = CGRectMake(x, y, width, height);
        } else {
            /*这个颇为“巧”了,是重用上面的布局,
             */
            UICollectionViewLayoutAttributes *lastAttrs = self.attrsArray[i - 6];
            CGRect lastFrame = lastAttrs.frame;
            lastFrame.origin.y += 2 * width;
            attrs.frame = lastFrame;
        }
        // 添加UICollectionViewLayoutAttributes
        [self.attrsArray addObject:attrs];
    }
}
/**
 * 返回collectionView的内容大小
 */
- (CGSize)collectionViewContentSize
{
    int count = (int)[self.collectionView numberOfItemsInSection:0];
    int rows = (count + 3 - 1) / 3;
    CGFloat rowH = self.collectionView.frame.size.width * 0.5;
    return CGSizeMake(0, rows * rowH);
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

3,根据collectionView的item来更新其高度

有时候的需求只是把collectionview当做某个界面的小小一部分。所以就需要根据内容item来更新父级frame(主要就是高度),但我们知道collectionview的布局可谓是五花八门,(说好听点)有时会根据屏幕自动适配,(说难听点)就是item乱动。
那么正因为item的多种布局,导致contentSize的不确定,我们该如何根据最终的contentSize,来确定collection View的frame,甚至以此来更新父view的frame呢?

问题的突破口是我们要确定contentsize的frame,其实主要用到的是高度,

以下就以高度为例提供一个思路吧

在定义的UICollectionViewFlowLayout里面
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray * layoutAttributes_t = [super layoutAttributesForElementsInRect:rect];
    NSArray * layoutAttributes = [[NSArray alloc]initWithArray:layoutAttributes_t copyItems:YES];
    //用来临时存放一行的Cell数组
    NSMutableArray * layoutAttributesTemp = [[NSMutableArray alloc]init];
    for (NSUInteger index = 0; index < layoutAttributes.count ; index++) {

        //防闪
        UICollectionViewLayoutAttributes *currentAttr = layoutAttributes[index]; // 当前cell的位置信息,这个当前的意思是当前index的,别误解了咯
        UICollectionViewLayoutAttributes *previousAttr = index == 0 ? nil : layoutAttributes[index-1]; // 上一个cell 的位置信
        UICollectionViewLayoutAttributes *nextAttr = index + 1 == layoutAttributes.count ?
        nil : layoutAttributes[index+1];//下一个cell 位置信息
        
        //加入临时数组
        [layoutAttributesTemp addObject:currentAttr];
        _sumWidth += currentAttr.frame.size.width;
        
        if (index == layoutAttributes.count - 1){
            //主要是这里了,拿最后的那个,从而大致的得到其content size的高度
            _sumHeight = currentAttr.frame.origin.y + currentAttr.frame.size.height;
            
            
            NSDictionary *dict = @{collectionAllItemHeight:@(_sumHeight)};
            
            [[NSNotificationCenter defaultCenter] postNotificationName:collectionAllItemHeight object:nil userInfo:dict];
            
        }
        //CGRectGetMaxY可以直接拿y的哟
        CGFloat previousY = previousAttr == nil ? 0 : CGRectGetMaxY(previousAttr.frame);
        CGFloat currentY = CGRectGetMaxY(currentAttr.frame);
        CGFloat nextY = nextAttr == nil ? 0 : CGRectGetMaxY(nextAttr.frame);
        //如果当前cell是单独一行
        if (currentY != previousY && currentY != nextY){
        }
        //如果下一个cell在本行,这开始调整Frame位置
        else if( currentY != nextY) {
        }
    }

    return layoutAttributes;
}

        

就是在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect方法中拿到最后一个item,进而根据其拿到大概content size的高度,通过通知的形式发布出去给外部使用。

你可能感兴趣的:(UICollectionView(摘要版))