0
点赞
收藏
分享

微信扫一扫

iOS CollectionView 进阶

小龟老师 2021-09-19 阅读 72

前言

这篇文章讲一下CollectionView的高级用法,比如自定义布局

自定义布局

先写个入门的布局代码:

import UIKit

class ViewController: UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {

    var flowLayout : UICollectionViewFlowLayout?
    var collectionView : UICollectionView?

    var items : [[String]]?

    override func viewDidLoad() {
        super.viewDidLoad()

        buildData()

        flowLayout = UICollectionViewFlowLayout.init()
        flowLayout?.minimumLineSpacing = 20
        flowLayout?.minimumInteritemSpacing = 10
        flowLayout?.itemSize = CGSize(width: 65, height: 35)
        flowLayout?.scrollDirection = .vertical
        flowLayout?.headerReferenceSize = CGSize(width: 150, height: 50)
        flowLayout?.footerReferenceSize = CGSize(width: 130, height: 50)
        flowLayout?.sectionInset = .init(top: 10, left: 10, bottom: 10, right: 10)

        let screenBounds = UIScreen.main.bounds;
        let collectionFrame = CGRect(x: 0, y: 50, width: screenBounds.width, height: screenBounds.height-100)
        collectionView = UICollectionView(frame: collectionFrame, collectionViewLayout: flowLayout!)
        collectionView?.backgroundColor = .gray
        collectionView?.alwaysBounceVertical = true
        view.addSubview(collectionView!)

        collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionView?.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")
        collectionView?.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "footer")

        collectionView?.dataSource = self
        collectionView?.delegate = self
    }

    private func buildData() {
        items = [["1:1","1:2","1:3","1:4","1:5","1:6","1:7","1:8","1:9","1:10","1:11","1:12","1:13","1:14","1:15","1:16"],["2:1","2:2","2:3","2:4","2:5","2:6","2:7","2:8","2:9","2:10","2:11","2:12","2:13","2:14","2:15","2:16","2:17","2:18","2:19","2:20"]]
    }

    //MARK:- data source

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items!.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items![section].count
    }

    //MARK:- delegate

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .green

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        if kind == UICollectionView.elementKindSectionHeader {
            let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header", for: indexPath)
            header.backgroundColor = .blue
            return header
        } else if kind == UICollectionView.elementKindSectionFooter {
            let footer = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "footer", for: indexPath)
            footer.backgroundColor = .orange
            return footer
        } else {
            return UICollectionReusableView()
        }
    }
}

效果如下:


  1. dequeueReusableCell(withReuseIdentifier identifier: String, for indexPath: IndexPath)这个方法一定会返回一个cell,前提是identifier要注册过,不然抛异常
  2. forSupplementaryViewOfKind的参数虽然是一个String,但需要传UICollectionView.elementKindSectionHeaderUICollectionView.elementKindSectionFooter
  3. flowLayout可以直接设置headerReferenceSize/footerReferenceSize,这是default size,但是如果实现了代理collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int),会使用代理返回的值。重点是,如果是vertical滚动,只会用高度,宽度和collectionView一致,horizental滚动反过来
  4. UICollectionViewCell默认没有title等元素,完全一个白板,需要自己加UI组件(和UITableViewCell不同,后者默认有title等UI元素

自定义布局

我们都知道要使用CollectionView离不开两个类:UICollectionViewUICollectionViewLayout,其中后者掌握了CollectionView的布局。实际上,常用的有关布局的代理方法,比如sizeForItem的调用时机也是在UICollectionViewFlowLayoutprepare,注意,UICollectionViewFlowLayout自己实现的prepare中调用了sizeForItem代理,但是UICollectionViewLayout是不会自己调用prepare的,默认的是实现是空,举个例子吧:

重写UICollectionViewFlowLayoutprepare方法,并使用这个layout:

class CustomFlowLayout: UICollectionViewFlowLayout {
    override func prepare() {
        print("prepare1")
        super.prepare()
        print("prepare2")
        itemSize = CGSize(width: 120, height: 50)
    }
}

打印日志:

prepare1    # 调用super.prepare前
numberOfSections
numberOfItemsInSection
numberOfItemsInSection
sizeForItemAt
sizeForItemAt
referenceSizeForHeaderInSection
sizeForItemAt
sizeForItemAt
referenceSizeForHeaderInSection
prepare2    # 调用super.prepare后
cellForItemAt
cellForItemAt
cellForItemAt
cellForItemAt
viewForSupplementaryElementOfKind
viewForSupplementaryElementOfKind
viewForSupplementaryElementOfKind
viewForSupplementaryElementOfKind

可以看到UICollectionViewFlowLayoutprepare方法调用了numberOfSections & numberOfItemsInSection & sizeForItemAt & referenceSizeForHeaderInSection 等涉及到布局的代理方法

但是UICollectionViewLayoutprepare方法的默认实现是空函数

瀑布流Pinterest-Like布局

下面着手写一个瀑布流的demo:

class CustomCollectionLayout: UICollectionViewLayout {

    private var attrCache : [UICollectionViewLayoutAttributes] = []
    private let columnNum = 2
    override var collectionViewContentSize: CGSize {
        return CGSize(width: contentWidth, height: contentHeight)
    }
    private var contentWidth : CGFloat {
        guard let collectionView = collectionView else {
            return 0
        }

        let inset = collectionView.contentInset
        return collectionView.bounds.width - (inset.left + inset.right)
    }
    private var contentHeight : CGFloat = 0
    private let cellPadding : CGFloat = 5
    override func prepare() {
        print("prepare1")
        super.prepare()
        guard let collectionView = collectionView,
              attrCache.count == 0 else {
            return
        }

        var columns : [CGFloat] = Array.init(repeating: 0, count: columnNum)

        for idx in 0..<collectionView.numberOfItems(inSection: 0) {

            let offsetXIdx = idx % 2
            let x = CGFloat(offsetXIdx) * (contentWidth / 2)
            let y = columns[offsetXIdx]
            let width = contentWidth/2
            let height = randomHeight() + cellPadding * 2

            var frame = CGRect(x: x, y: y, width: width, height: height)
            frame = frame.insetBy(dx: cellPadding, dy: cellPadding)

            let indexPath = IndexPath(item: idx, section: 0)

            let collectionAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            collectionAttributes.frame = frame

            attrCache.append(collectionAttributes)

            columns[offsetXIdx] += height
        }

        contentHeight = columns.max()!
        print("prepare2")
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard attrCache.count > 0 else {
            return nil
        }

        return attrCache[indexPath.item]
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard attrCache.count > 0 else {
            return nil
        }

        var res : [UICollectionViewLayoutAttributes] = []

        for attr in attrCache {
            if attr.frame.intersects(rect) {
                res.append(attr)
            }
        }

        return res
    }

    private func randomHeight() -> CGFloat {
        let randomHeight = CGFloat.random(in: 50...150)
        print("randowm:\(randomHeight)")
        return randomHeight
    }
}

同样的,有以下几点需要注意的:

  1. 观察prepare方法加的log输出,并没有调用sizeForItem代理方法
  2. contentHeight直到prepare结束才确定的
  3. cacheAttr的原因:

demo截图如下:

结交人脉

  • ——点击加入:iOS开发交流群
    以下资料在群文件可自行下载

    驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!**

举报

相关推荐

0 条评论