美文网首页iOS Developer
关于自定义瀑布流的布局(Swift)

关于自定义瀑布流的布局(Swift)

作者: 村长大人tardis_cxx | 来源:发表于2017-05-14 17:39 被阅读759次

    瀑布流 UICollectionView Swift


    最近整理以前iOS开发中用到的功能代码,发觉对瀑布流的布局有点新的认识,现记述如下,也方便对此不熟悉的小伙伴们作以参考。
    关于瀑布流布局,我有两种思路,一种是使用UIScrollView,一种是使用UICollectionView,但UIScrollView的话,要考虑每个cell的循环复用问题,带来的开发问题比较多。如果使用UICollectionView,就会很方便地实现瀑布流布局

    思路

    • 通过UICollectionView的特性,我们可以容易实现cell的布局,也不需要考虑循环复用的问题,但每个cell的布局都是一样的,frame是固定死的。不过深入研究后,决定UICollectionView布局的是UICollectionViewLayout这个类。
    • 那么我们是否可以重写UICollectionViewLayout的子类UICollectionViewFlowLayout来自定义布局呢
    • 答案显然是可以的,我们可以仿照UICollectionViewFlowLayout重新定义一个瀑布流布局。如果这样,显然要耗费不少时间,而且也没有必要,这里,我重新定义一个布局,继承自UICollectionViewFlowLayout,重写一些函数和属性,来实现瀑布流布局

    重要代码

    UICollectionView布局的时候,总是会调用prepare()函数,那么我们重写这个函数:

    override func prepare() {
            super.prepare()
            
            guard let collectionView = collectionView else {
                return
            }
            let count = collectionView.numberOfItems(inSection: 0)
            
            let itemW = (collectionView.bounds.width - sectionInset.left - sectionInset.right - (CGFloat(cols) - 1) * minimumInteritemSpacing) / CGFloat(cols)
            var itemH: CGFloat = 0
            var itemX: CGFloat = 0
            var itemY: CGFloat = 0
            
            for i in cellAttributes.count..<count {
                let indexPath = IndexPath(item: i, section: 0)
                let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                
                itemH = dataSource?.waterfallLayout(self, indexPath: indexPath) ?? 0
                let minH = colHeight.min()!
                let minIndex = colHeight.index(of: minH)!
                itemX = sectionInset.left + CGFloat(minIndex) * (itemW + minimumInteritemSpacing)
                itemY = minH
                
                attr.frame = CGRect(x: itemX, y: itemY, width: itemW, height: itemH)
                
                colHeight[minIndex] = attr.frame.maxY + minimumLineSpacing
                cellAttributes.append(attr)
            }
    }
    

    cell中布局的实质是UICollectionViewLayoutAttributes决定着每个cell的frame,那么就需要重写layoutAttributesForElements()这个函数,用来返回我们自定义的布局:

     override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            return cellAttributes
     }
    

    collectionView 之所以能滚动是因为有滚动范围,那么我们依然重写collectionViewContentSize这个计算型属性:

    override var collectionViewContentSize: CGSize {
            return CGSize(width: 0, height: colHeight.max()! + sectionInset.bottom - minimumLineSpacing)
    }
    

    除此之外,还需要定义一些私有和公开属性:

        /// 数据源
        weak var dataSource: TCWaterfallLayoutDataSource?
        /// 列数
        fileprivate lazy var cols: Int = self.dataSource?.waterfallLayoutWithCols?(self) ?? 2
        /// cell属性数组
        fileprivate lazy var cellAttributes: [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
        /// 缓存高度
        fileprivate lazy var colHeight: [CGFloat] = Array(repeatElement(self.sectionInset.top, count: self.cols))
    

    既然有数据源属性,那么就得有数据源协议,这里定义了2个函数,一个返回cell高度,一个返回的是UICollectionView显示的列数:

    @objc protocol TCWaterfallLayoutDataSource: class {
        func waterfallLayoutForHeight(_ layout: TCWaterfallLayout, indexPath: IndexPath) -> CGFloat
        @objc optional func waterfallLayoutForCols(_ layout: TCWaterfallLayout) -> Int
    }
    

    如此,创建UICollectionView 的时候,就可以把布局换成自定义的布局了,实现数据源,就会有以下效果:

    效果图.png

    如有错误,还望指正!

    相关文章

      网友评论

        本文标题:关于自定义瀑布流的布局(Swift)

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