UICollectionViewLayout实现历史搜索标签排版

在写商品搜索功能,包括历史搜索记录和热门搜索,这里面用到了文字标签自动排版,所以研究了一下UICollectionViewLayout。
UICollectionViewLayout是对CollectionView的布局和行为进行描述的类,这是CollectionView和TableView的重要区别,通过这个类,可以根据需要调整cell的布局,一般瀑布流用collectionView来实现。

UICollectionViewLayout是一个需要子类化的抽象基类,布局对象(layout object)决定了cell,Supplementary Views(追加视图)和Decoration Views(装饰视图) 在collection view 内部的位置,在collection view获取的时候提供这些布局信息。布局对象主要用来提供位置和状态信息,它并不负责创建视图,cell、追加视图、装饰视图是由collection view的数据源(data source)创建的。

自定义UICollectionViewLayout

自定义的layout要继承UICollectionViewLayout类,然后重载下列方法:
- (CGSize)collectionViewContentSize;
1.返回值表示所有内容的尺寸,决定了collection view的滚动范围
2.自动调用,子类必须重载该方法

- (void)prepareLayout;
1.首次布局和之后重新布局的时候会调用,并不会每次滑动都调用
2.当数据源变化时也会调用
3.自动调用

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
1.返回rect(可见范围内)中所有元素的布局属性,当collection view 滑动时会再调用这个方法获取布局属性
2.UICollectionViewLayoutAttributes包含cell或追加视图或装饰视图的布局信息
3.在创建UICollectionViewLayoutAttributes对象时,要根据视图是cell还是supplementary还是decoration来创建attributes对象
    layoutAttributesForCellWithIndexPath:
    layoutAttributesForSupplementaryViewOfKind:withIndexPath:
    layoutAttributesForDecorationViewOfKind:withIndexPath:
4.自动调用,子类必须重载该方法

-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
1.返回对应于indexPath的位置的cell的布局属性
2.不会自动调用,在需要的时候用UICollectionViewLayout对象触发该方法,不需要的话可以不重写该方法

-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
1.返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载
2.在需要的地方,用UICollectionViewLayout对象调用该方法

-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
1.返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
2.在需要的地方,用UICollectionViewLayout对象调用该方法

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。

项目中的应用

我这个搜索模块中用的collection view是有分组的,所以自定义的UICollectionViewLayout中,要计算cell和Supplementary Views(追加视图)的布局。
在prepareLayout中,把所有元素的属性计算好,存到attributesArray数组中。

- (void)prepareLayout {
    [super prepareLayout];
    attributesArray = [NSMutableArray array];
    ......(省略^_^)
    self.scrollDirection = UICollectionViewScrollDirectionVertical;
    

    NSInteger section = [self.collectionView numberOfSections];
    for (int i = 0; i < section; i++) {
        NSInteger item = [self.collectionView numberOfItemsInSection:i];
        if (item) {
            // 分组头视图的布局属性 加入数组中,才起作用
            UICollectionViewLayoutAttributes *sectionAtt = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
            [attributesArray addObject:sectionAtt];
        }
        for (int j = 0; j < item; j++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
            // 每个cell的布局属性 加入数组中
            UICollectionViewLayoutAttributes *att = [self layoutAttributesForItemAtIndexPath:indexPath];
            [attributesArray addObject:att];
        }
    }
    
}

prepareLayout计算出的attributesArray,包含所有视图的布局属性,在以下方法中返回。

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return attributesArray;
}

重写layoutAttributesForSupplementaryViewOfKind:atIndexPath:,在prepareLayout方法中调用了这个方法,其实追加视图布局属性就是在这个方法中计算出来的。

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attri = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];
    /*
     [super layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath];
     通过super 创建的对象是nill
     */

    if (orgin.lineX > contentInsets.left) {
        orgin.totalY += itemHeight+lineSpace;
    }
    attri.frame = CGRectMake(0, orgin.totalY, self.collectionView.width, sectionHeight);
    orgin.totalY += sectionHeight;
    
    return attri;
}

在下面这个方法中,计算了indexPath位置的cell的布局。同样在prepareLayout中调用了这个方法,将所有item的UICollectionViewLayoutAttributes加到数组中。

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attr = [super layoutAttributesForItemAtIndexPath:indexPath];
    /*这里用[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]创建attr也是可以的。这个地方用super 方法创建的attr不是nill,但是上面那个追加视图里面用super 就不可以,目前还在分析原因,希望知道的简友,可以告诉我^_^*/
    // 进入下一个分组
    if (orgin.section != indexPath.section) {
        orgin.section = indexPath.section;
        orgin.lineNumber ++;
        orgin.lineX = contentInsets.left;
    }
    
    //获得当前item的标题
    NSString *title = [self.dataSource titleForLabelAtIndexPath:indexPath];
    CGSize size = [self sizeWithTitle:title font:titleFont];
    
    CGFloat itemWidth = size.width+itemMargin;
    // 标签的最大宽度
    if (itemWidth > CGRectGetWidth(self.collectionView.frame)-(contentInsets.left+contentInsets.right)) {
        itemWidth = CGRectGetWidth(self.collectionView.frame)-(contentInsets.left+contentInsets.right);
    }
    if (itemWidth > CGRectGetWidth(self.collectionView.frame)-contentInsets.right-orgin.lineX) {
        orgin.lineNumber ++;
        orgin.lineX = contentInsets.left;
        orgin.totalY += itemHeight+lineSpace;
    }
    
    CGFloat itemOrginX = orgin.lineX;
    CGFloat itemOrginY = orgin.totalY;
    
    attr.frame = CGRectMake(itemOrginX, itemOrginY, itemWidth, itemHeight);
    orgin.lineX += itemWidth+itemSpace;
    
    return attr;
}

origin对象是一个结构体,lineX记录了即将布局的item的x值;lineNumber记录即将布局的item是第几行,之前用这个值计算y值,现在没什么用了;totalY是即将布局的item的y值,最后totalY就是最后一个item的y值;section记录当前分组。

typedef struct currentOrigin {
    CGFloat     lineX;
    NSInteger   lineNumber;
    NSInteger   section;    // 记录当前分组
    CGFloat     totalY;     // collectionView 内容的最大Y值
}currentOrigin;
- (CGSize)sizeWithTitle:(NSString *)title font:(UIFont *)font {
    CGRect rect = [title boundingRectWithSize:CGSizeMake(1000, itemHeight) options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil];
    return rect.size;
}

// 返回collection view 内容的尺寸,决定了collection是否可以滑动
- (CGSize)collectionViewContentSize {
    CGFloat sizeHeight = orgin.lineX > contentInsets.left ? orgin.totalY+ itemHeight+lineSpace : orgin.totalY;
    return CGSizeMake(self.collectionView.width, sizeHeight);
}
demo源码:https://github.com/celiaDeveloper/XDEShop
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容