美文网首页
UICollectionViewLayout实现历史搜索标签排版

UICollectionViewLayout实现历史搜索标签排版

作者: 走在长长地路上 | 来源:发表于2018-04-16 16:01 被阅读431次

在写商品搜索功能,包括历史搜索记录和热门搜索,这里面用到了文字标签自动排版,所以研究了一下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

相关文章

网友评论

      本文标题:UICollectionViewLayout实现历史搜索标签排版

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