美文网首页iOS相关工作生活
iOS 自定义布局CollectionViewCell

iOS 自定义布局CollectionViewCell

作者: 天下林子 | 来源:发表于2019-07-03 22:28 被阅读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;
    
    
    • 在这上面是有4个,在实现该需求中只使用前两个就可以了,看API的注释,就知道,是说,UICollectionView调用这四个方法来确定布局信息,通过实现-layoutAttributesForElementsInRect:返回辅助视图或装饰视图的布局属性,或以屏幕上需要的方式执行布局,且所有的item都可以通过indexPath进行布局属性实例,说白了,一句话就是通过上面的方法,你可以自己随心所欲的布局collecTionViewCell。

    • 在layoutAttributesForElementsInRect:方法中,可以通过rect获取到你创建的所有colleViewCell中的attribute

    - (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;
    }
    
    
    • 在layoutAttributesForItemAtIndexPath方法中,可以根据indexPath对单个的cell进行设置布局,就是修改cell的frame,而在该需求中是直接计算好cell的frame的值,然后直接重新创建,然后返回创建好的UICollectionViewLayoutAttributes。代码如下:
    #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,哈哈哈~~~

    相关文章

      网友评论

        本文标题:iOS 自定义布局CollectionViewCell

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