UICollectionViewFlowLayout实现横向滑动

2,103 阅读4分钟

Swift - 自定义UICollectionViewFlowLayout实现横向滑动

最近要实现collection横向滑动功能,看了很多代码,但感觉功能不太齐全,基本只实现了基础设置,一旦要求其他分区控制item大小就不行了,现在补充下,希望能帮到大家。

要实现横向滑动功能,首先我们要了解FlowLayout的实现

如图:

layout.png 在collectionView默认设置下row的显示是这样的(layout.scrollDirection = .vertical),即竖向显示,上下拉动。通常情况下能满足我们大部分的业务需求,当我们需要做类似微信表情左右滑动功能,就不能用这个了。尝试设置属性layout.scrollDirection = .horizontal会出变成这样。如图:

layout.png 并且按照分页滑动的时候回出现上页的边角,无法满足我们的需求。这时候我们就需要自定义FlowLayout,创建类WorkbenchCommonlyLayOut继承于UICollectionViewFlowLayout。

1、重写override func prepare() {}方法,这个方法主要用于计算控件布局。

2、重写func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {},这个方法主要用于返回rect中所有元素的布局属性

3、重新计算override var collectionViewContentSize: CGSize{ return CGSize(width: maxWidth, height: maxHeight) }如果不重新计算宽高,会出现最后一页显示不全的情况

下面上代码:(吐槽下:代码拷贝进来会自动出现**号,大家自动省略就行 我实在是不想删了)

    var cols : Int = 0 //列
    // 重新计算上下间距
    var rewriteminimumInteritemSpacing: CGFloat = 0
    var rewriteminimumLineSpacing: CGFloat = 0
    // 重新计算上下左右间距
   var rewriteTop: CGFloat = 0
   var rewriteBottom: CGFloat = 0
   var rewriteLeft: CGFloat = 0
   var rewriteRight: CGFloat = 0

    fileprivate lazy var cellAttrs : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()

    fileprivate lazy var maxWidth : CGFloat = 0
    fileprivate lazy var maxHeight: CGFloat = 0
    **var** benchdelegate:WorkbenchCommonlyLayOutDelegate?
    // 重写布局

    **override** **func** prepare() {
        **super**.prepare()
        // 直接设置layout的值
        sectionInset.top = rewriteTop
        sectionInset.left = rewriteLeft
        sectionInset.right = rewriteRight
        sectionInset.bottom = rewriteBottom
        minimumInteritemSpacing = rewriteminimumInteritemSpacing
        minimumLineSpacing = rewriteminimumInteritemSpacing
        //0.计算item的宽度和高度
        itemSize.width = ((collectionView?.bounds.width)! - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(cols - 1)) / CGFloat(cols)
        itemSize.height = ((collectionView?.bounds.height)! - sectionInset.top - sectionInset.bottom - minimumLineSpacing * CGFloat(rows - 1)) / CGFloat(rows)
        //1.获取一共多少个组
        **let** sectionCount = collectionView!.numberOfSections
        //2.获取每个组中有多少个item
        **var** prePageCount : Int = 0    //页数
        **for** i **in** 0..<sectionCount {
            **let** itemCount = collectionView!.numberOfItems(inSection: i)
            **for** j **in** 0..<itemCount {
                //2.1获取cell对应的indexPath把item和section对调(可以用系统的API看看效果就明白了)
                **let** indexpath = IndexPath(item: j, section: i)
                //2.2根据indexPath创建UICollectionViewLayoutAttributes
                **let** attr = UICollectionViewLayoutAttributes(forCellWith: indexpath)
                // 2.3.计算j在该组中第几页
                **let** page = j / (cols * rows)
                **let** index = j % (cols * rows)
                //2.4设置attrs的frame
                **let** itemY = sectionInset.top + (itemSize.height + minimumLineSpacing) * CGFloat(index / cols)
                **let** itemX = CGFloat(prePageCount + page) * collectionView!.bounds.width +  sectionInset.**left** + (itemSize.width + minimumInteritemSpacing) * CGFloat(index % cols)
                attr.frame = CGRect(x: itemX, y: itemY, width: itemSize.width, height: itemSize.height)
\
                //2.5加入到数组中
                cellAttrs.append(attr)
            }
            prePageCount += (itemCount - 1) / (cols * rows) + 1
        }
        //计算最大宽度 CGFloat(70*indexline+10*(indexline-1)+20)
        maxWidth = CGFloat(prePageCount) * collectionView!.bounds.width
        maxHeight = CGFloat(rows * Int(itemSize.height) + Int(minimumLineSpacing) * (rows - 1) + Int(sectionInset.top) + Int(sectionInset.bottom))
        benchdelegate!.collectionView(CGSize(width: maxWidth, height: maxHeight))
    }

    //返回rect中的所有的元素的布局属性
    **override** **func** layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        **return** cellAttrs
    }

    //确定collectionView的所有内容的尺寸
    **override** **var** collectionViewContentSize: CGSize{
        **return** CGSize(width: maxWidth, height: maxHeight)

    }  
    

这时候我们已经完成基本的功能需求了, 初始化并设置值


        **let** layout = WorkbenchCommonlyLayOut.init()
        layout.benchdelegate = **self**
        layout.rows = 5
        layout.cols = 3
        layout.rewriteTop = 10
        layout.rewriteLeft = 15
        layout.rewriteRight =  15
        layout.rewriteBottom = 10
        layout.rewriteminimumInteritemSpacing = 10
        layout.rewriteminimumLineSpacing = 7
        **let** indexline = 3
        **let** collectView = UICollectionView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height), collectionViewLayout: layout)
        collectView.backgroundColor = AuColor.augroundColor
        collectView.delegate = **self**
        collectView.dataSource = **self**
        collectView.showsHorizontalScrollIndicator = **false**
        collectView.isPagingEnabled = **true**
        collectView.isScrollEnabled = **true**
        collectView.register(UINib.init(nibName: AUWorkbenchCommonlyCell.reuseIndentifier, bundle: Bundle.main), forCellWithReuseIdentifier: AUWorkbenchCommonlyCell.reuseIndentifier)
        **return** collectView

    }()

记住要实现delegate跟dataSource(偷懒下就不上代码了),最终效果图如下:

截屏2021-10-20 下午6.08.18.png

截屏2021-10-20 下午6.08.25.png 这个是左右滑动的本来想上个录制好的小视频,结果无法上传。 当然这样就完了么,当然不是,我们可以看到UICollectionViewFlowLayout是有代理的通过代理我们可以控制每个section中item的大小,如果按照上面的方法,是没办法实现的。 所以我们要在自定义的flowlayout写类似系统的代理,下面上代码

**protocol** WorkbenchCommonlyLayOutDelegate: AnyObject {
    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
    **func** collectionView(_ size: CGSize)

}

然后在func prepare()方法中调用代理(就是上面实现的代码,放在获取indexpath下面就行)

// 通过代理获取值

                // 获取item值
                itemSize = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, sizeForItemAt: indexpath))!
                // 获取上下左右的UIEdgeInsets值
                sectionInset = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, insetForSectionAt: indexpath.section))!
                // 获取上下间距
                minimumLineSpacing = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, minimumLineSpacingForSectionAt: indexpath.section))!
                // 获取左右间距
                minimumInteritemSpacing = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, minimumInteritemSpacingForSectionAt: indexpath.section))!

然后实现代理WorkbenchCommonlyLayOutDelegate

最后再运行一次:如图

截屏2021-10-20 下午6.22.33.png

截屏2021-10-20 下午6.22.46.png

截屏2021-10-20 下午6.22.53.png

上面设置了2个section

// 重写layoutdelegate

    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        **if** indexPath.section == 1 {
            **return** CGSize(width: 50, height: 70)
        }
        **return** CGSize(width: CGFloat((Int(GK_SCREEN_WIDTH)-15*2-(5-1)*Int(rewriteminimumInteritemSpacing))/5), height: 70)
    }

    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        **return** UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
    }

    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        **return** rewriteminimumLineSpacing;
    }

    // 左右间距
    **func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        **return** rewriteminimumInteritemSpacing;
    }

    **func** collectionView(_ size: CGSize) {
        collectView.frame.size.height = size.height
    }

小小的偷懒下,没有给行列做代理,有需求的话自己在WorkbenchCommonlyLayOutDelegate实现代理传入行列这样也能实现控制不同分区的行列。现在基本完成系统本身拥有的功能。有其他特殊需求的可以自行修改。