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<UICollectionViewLayoutAttributes *> *)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.count;i++){
        UICollectionViewLayoutAttributes *atts = elementsArray[I];
        // cell的中心点x 和 collectionView最中心点的x值 的间距
        CGFloat delta = ABS(atts.center.x - centerX);
        //根据间距值计算cell的缩放比例
        CGFloat scale = 1 - delta/self.collectionView.frame.size.width;
              );
        //设置缩放比例
        atts.transform = CGAffineTransformMakeScale(scale, scale);
    }
    return elementsArray;
}

首先要吐槽的是:
为什么是处理数组(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的高度,通过通知的形式发布出去给外部使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容