iOS相关工作生活

iOS 自定义布局CollectionViewCell

2019-07-03  本文已影响0人  天下林子

前言

最近接了一个需求,就是在App中的搜索界面,用户可能会在搜索输入框输入或者很长或者很短的文字,然后开发者要对用户之前输入的内容要进行记录,就像淘宝以及京东等等App里面的搜索页面的效果一样,看图🔽

image.png

看上面的搜索记录,有的是一行显示2个,有的是4个,或者只显示一个等等,情况都不一样,那么作为开发者的你,可能会有2个思路,一个是直接for循环来创建,计算内容宽度,如果大于屏幕宽度就换行呗,另一种是稍微高端一点的玩法就是使用UIcollectionView完全自定义实现Cell。可能路过的大佬也有其他更牛的想法,请大佬一定留言赐教,在此,先谢过各位大佬O(∩_∩)O哈哈~

先看下效果


image.png

实现方式一

-----来了老弟-----
对于第一种方式直接for循环的创建,我这就不多说了,思路很简单就是先计算出内容的宽度,然后与屏幕宽度做比较,在此我就直接show me code~~~

CGFloat markViewX = newCountWidth(45);
    CGFloat btnMarkX = markViewX;
    CGFloat markViewMaxW = SCREEN_WIDTH - markViewX * 2;
    CGFloat btnMarkY = newCountWidth(98);
    
    for (int x = 0; x < self.historyList.count; x++)
    {
        NSString *hotword = [self.historyList pf_objWithIndex:x];
        NSString * btnText = [NSString stringWithFormat:@"   %@   ",hotword];
        
        //搜索内容按钮
        UIButton * btnMark = [UIButton buttonWithType:UIButtonTypeCustom];
        btnMark.layer.cornerRadius = newCountWidth(10);
        btnMark.layer.masksToBounds = YES;
        btnMark.backgroundColor = [UIColor colorFromHexString:@"#ecf0f6"];
        btnMark.titleLabel.font = [UIFont systemFontOfSize:newCountWidth(39)];
        [btnMark setTitleColor:[UIColor colorFromHexString:@"#333333"] forState: UIControlStateNormal];
        [btnMark setTitle: btnText forState:UIControlStateNormal];
        btnMark.tag = 2000 + x ;
        [btnMark addTarget: self action:@selector(_clickBtnClick:) forControlEvents:UIControlEventTouchUpInside];
        
        CGSize textSize = [btnText sizeWithfont:[UIFont systemFontOfSize:newCountWidth(39)] constrainedToSize: CGSizeMake(0, newCountWidth(96))];
        
        if (btnMarkX + textSize.width >= markViewMaxW)
        {
            btnMarkX = markViewX;
            btnMarkY += newCountWidth(96) + newCountWidth(27);
        }
        
        btnMark.frame = CGRectMake(btnMarkX, btnMarkY, textSize.width, newCountWidth(96));
        [self addSubview:btnMark];
        
        btnMarkX += textSize.width + newCountWidth(28);
    }

其实代码也不多,仅供参考,精彩在后面~

实现方式二

使用UICollectionView,完全自定义cell的显示布局,对于collectionView的基本创建在此就不多说了,最关键的就是UICollectionViewFlowLayout,我们都知道要想自己去布局collectionViewCell,那就必须想法对UICollectionViewFlowLayout下手,在UICollectionViewFlowLayout中,我们可以使用下面的Api

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *array = [super layoutAttributesForElementsInRect:rect];

    NSMutableArray *itemArray = [NSMutableArray arrayWithCapacity:array.count];

    for (UICollectionViewLayoutAttributes *attrs in array) {
        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:attrs.indexPath];
        [itemArray addObject:attr];
    }

    return itemArray;
}

#pragma mark - 处理单个的Item的layoutAttributes
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //collectionView 距离父视图左边的距离
    CGFloat x = self.sectionInset.left;
    
    //collectionView 距离父视图顶部的距离
    CGFloat y = self.sectionInset.top;
    
    //判断获得前一个cell的row
    NSInteger preRow = indexPath.row - 1;
    
    if(preRow >= 0)
    {
        if(self.yFrameArray.count > preRow)
        {
            x = [self.xFrameArray[preRow] floatValue];
            y = [self.yFrameArray[preRow] floatValue];
        }
        
        NSIndexPath *preIndexPath = [NSIndexPath indexPathForItem:preRow inSection:indexPath.section];
        CGFloat preWidth = [self.delegate obtainItemWidth:self widthAtIndexPath:preIndexPath];
        
        x += preWidth + self.minimumInteritemSpacing;
    }
    
    //获取cell的宽度
    CGFloat currentWidth = [self.delegate obtainItemWidth:self widthAtIndexPath:indexPath];;
    
    //保证一个cell不超过最大宽度
    currentWidth = MIN(currentWidth, self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right);
    
    //根据cell的宽度+间距 计算cell的x和y坐标,如果大于一行则换行,否则不换行
    if(x + currentWidth > self.collectionView.frame.size.width - self.sectionInset.right)
    {
        //超出范围,换行 计算x值, y值
        x = self.sectionInset.left;
        y += _rowHeight + self.minimumLineSpacing;
    }
    
    // 创建属性设置cell的frame
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attrs.frame = CGRectMake(x, y, currentWidth, _rowHeight);
    
    /*
     按照row将cell的frame的x和y插入进去
     也可以使用insertObject:方法
     */
    self.xFrameArray[indexPath.row] = @(x);
    self.yFrameArray[indexPath.row] = @(y);
    
    //NSLog(@"___xFrameArray___%@________yFrameArray___%@", self.xFrameArray, self.yFrameArray);
    
    return attrs;
}

上面实现的主要思路就是,如果想要实现根据不同的内容宽度进行换行操作,其实就是计算好cell的frame的x值和y值,这里我们用两个数组来分别存储cell的frame的x和y,先获取到前一个cell的row,然后根据row拿到前一个cell的frame的x和y,然后用其x加上collectionView的间距minimumInteritemSpacing和内容宽度,然后和屏幕宽度比较一下,是不是要换行,如果大于屏幕宽度,则换行,x的值为sectionInset.left,就是collectionView距离父视图左边的距离,y值则是高度加上cell之间的行间距,计算好之后,将x,y,width,height,分别赋值给一个新创建的UICollectionViewLayoutAttributes,然后返回给layoutAttributesForItemAtIndexPath方法。
具体代码可参考,注释也很详细--------> 点我

swift 版本来实现该需求

使用swift来实现呢,其实思路是完全一样的,只是语法上是完全是不一样的,😝,毕竟是新进贵妃,最近又出来个SwiftUI,又是红了一把,堪比两宋离婚,李晨分手啊·有钱人的世界就是分分合合,我的世界只有“hello world”,说远了
核心代码放下面:

extension PFCollectionViewFlowLayout {
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        //var attributes : NSMutableArray = super.layoutAttributesForElements(in: rect) as! NSMutableArray
        
        let array = super.layoutAttributesForElements(in: rect)
        
        //var itemArray = Array(repeating: "", count: array!.count)
        
        var itemArray = Array<Any>()
        for attrs in array! {
            
            let att : UICollectionViewLayoutAttributes = layoutAttributesForItem(at: attrs.indexPath)!
            itemArray.append(att)
        }
        return array
    }
    
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        
        //collectionView 距离父视图左边的距离
        var x  = self.sectionInset.left
        //collectionView 距离父视图顶部的距离
        var y  = self.sectionInset.top
        //判断获得前一个cell的row
        let preRow = indexPath.row - 1
        
        if preRow >= 0 {
            
            if self.yFrameArray.count > preRow {
                x = self.xFrameArray[preRow] as! CGFloat
                y = self.yFrameArray[preRow] as! CGFloat
            }
        let preIndexPath = IndexPath.init()
        if let prewidth = self.delegate?.obtainItemWidth(layout: self, atIndexPath: preIndexPath) {
            x += prewidth + self.minimumLineSpacing
        }
    }
        
        
        
    //获取cell的宽度
        if let currentWidth = self.delegate?.obtainItemWidth(layout: self, atIndexPath: indexPath) {
            //保证一个cell不超过最大宽度
            let scrollViewFrame = self.collectionView?.frame.size.width
            
            let currentItemWidth = min(currentWidth, scrollViewFrame! - self.sectionInset.left - self.sectionInset.right)
            
            //根据cell的宽度+间距 计算cell的x和y坐标,如果大于一行则换行,否则不换行
            if x + currentItemWidth > scrollViewFrame! - self.sectionInset.right {
                
                //超出范围,换行 计算x值, y值
                x = self.sectionInset.left
                y += self.rowHeight
            }
            
            //创建属性设置cell的frame
            let attrs : UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
            attrs.frame = CGRect(x: x, y: y, width: currentWidth, height: self.rowHeight)
            
            /*
             按照row将cell的frame的x和y插入进去
             也可以使用insertObject:方法
             */
            self.xFrameArray.insert(x, at: indexPath.row)
            self.yFrameArray.insert(y, at: indexPath.row)
            
            return attrs
        }
        
        return nil;
    }
        
}

这里面实现思路也是一样的,也是通过下面这两个API

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

自己计算好cell的frame的x,y,width,height然后创建新的UICollectionViewLayoutAttributes,进行赋值,然后return,最新的Swift5 哦,在写swift时,有一个地方说下,就是根据文字内容计算文字宽度的时候,在网上查了下,swift5都报错了,后来改了下,下面放上代码:

let text : NSString  = NSString(string: item)
            
 let maxSize = CGSize(width: UIScreen.main.bounds.size.width - 2 * 30, height: CGFloat(MAXFLOAT))
            
 let ww = NSString(string: text).boundingRect(with: maxSize, options: .usesFontLeading, attributes: [NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 20)], context: nil).size.width
            

以上是全部内容,感谢各位大佬读到最后,然后感谢大佬们留个star,哈哈哈~~~

上一篇下一篇

猜你喜欢

热点阅读