使用collectionView實現(xiàn)瀑布流

使用collectionView實現(xiàn)瀑布流。

系統(tǒng)默認布局

在使用 collectionView 的時候,系統(tǒng)默認提供了 UICollectionViewFlowLayout 流水布局。

在 ViewController 中以懶加載的方式創(chuàng)建 collectionView ,并設置相關的布局。

/// collection View
fileprivate lazy var collectionView: UICollectionView = {
    // 使用系統(tǒng)默認的布局
    let layout = UICollectionViewFlowLayout()
    // 設置每個 section 的內(nèi)容的間距
    layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    // 垂直滾動情況下,表示同一行的兩個item之間的間距
    layout.minimumInteritemSpacing = 10
    // 垂直滾動情況下,表示連續(xù)行之間的行間距
    layout.minimumLineSpacing = 10

    let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
    collectionView.backgroundColor = UIColor.cyan
    return collectionView
}()

在 viewDidLoad() 方法中添加 collectionView。

override func viewDidLoad() {
    super.viewDidLoad()

    view.addSubview(collectionView)
}

此時運行程序,結果如下,只看到青色的背景顏色,并沒有看到其他的什么內(nèi)容。

如果要顯示內(nèi)容,我們需要實現(xiàn) collection view 的數(shù)據(jù)源,在上述的懶加載中設置 collection view 的數(shù)據(jù)源是 ViewController。

...
// 設置數(shù)據(jù)源
collectionView.dataSource = self
return collectionView

設置了數(shù)據(jù)源為控制器之后,控制器就要遵守 UICollectionViewDataSource 協(xié)議,并實現(xiàn)必須的數(shù)據(jù)源方法。

// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
    // 返回 item 的個數(shù)
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 50
    }

    // 返回要展示的 cell
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "kContentCellID", for: indexPath)
        cell.backgroundColor = UIColor.red
        return cell
    }
}

在 viewDidLoad() 方法中使用唯一標識對 cell 進行注冊。

override func viewDidLoad() {
    super.viewDidLoad()

    view.addSubview(collectionView)
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "kContentCellID")
}

注意,注冊用的唯一標識字符串一定要和數(shù)據(jù)源方法中取出cell用的唯一標識一樣。為了安全起見,把這個標識符設置為一個常量,并在注冊和獲取的地方更新代碼寫法。

/// cell 唯一標識
private let kContentCellID = "kContentCellID"

class ViewController: UIViewController {
    // ...
}

collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kContentCellID)

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kContentCellID, for: indexPath)

運行程序,此時就可以看到系統(tǒng)默認的布局的顯示方式

流水布局

如果要實現(xiàn)流水布局,就必須自定義布局。創(chuàng)建 HLWaterfallLayout.swift 文件,定義 HLWaterfallLayout 類,并繼承 UICollectionViewFlowLayout

class HLWaterfallLayout: UICollectionViewFlowLayout {
    ...
}

準備布局

重寫 prepare() 方法,這個方法告訴 layout 對象更新當前的布局,它的默認實現(xiàn)不執(zhí)行任何操作。子類可以覆蓋它,并使用它來設置數(shù)據(jù)結構或執(zhí)行任何初始計算,以便稍后執(zhí)行布局。

/// 準備布局
    override func prepare() {
        super.prepare()

}

在這個方法設置 collection view 的 item 的一些屬性,主要就是設置 item 的frame。而 item 的屬性是由 UICollectionViewLayoutAttributes 對象管理的,也就是說每一個 item (cell) 對應著一個 UICollectionViewLayoutAttributes 對象。創(chuàng)建 UICollectionViewLayoutAttributes 對象,并設置該對象的 frame,就是對 cell 進行布局。

// 創(chuàng)建 UICollectionViewLayoutAttributes
let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)

需要參數(shù) indexPath, 創(chuàng)建indexPath

// 創(chuàng)建 indexPath
let indexPath = IndexPath(item: i, section: 0)

需要參數(shù) i ,也就是具體到每一個cell的索引,此時需要知道item的個數(shù)。

// 獲取 cell 的個數(shù)
let cellCount = collectionView!.numberOfItems(inSection: 0)
for i in 0..<cellCount {
    // 創(chuàng)建 indexPath
    let indexPath = IndexPath(item: i, section: 0)
    // 創(chuàng)建 UICollectionViewLayoutAttributes
    let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
}

設置 attr 的 frame 屬性,frame 屬性決定了 cell 的布局方式。

定義變量來記錄列數(shù),定義數(shù)組來記錄每一列的高度。

/// 列數(shù)
fileprivate lazy var cols: Int = 3
/// 每一列的高度
fileprivate lazy var cellMaxYs: [CGFloat] = Array(repeating: self.sectionInset.top, count: self.cols)

根據(jù)要展示的列數(shù)來計算cell的寬度,設置cell的高度為隨機值。cell的Y值為列中高度最低的列加上行間距。cell的X值是列中高度最低的列所在的X值。設置frame之后,更新數(shù)組中最低高度列的值。

let cellW: CGFloat  = (collectionView!.bounds.size.width - sectionInset.left - sectionInset.right - CGFloat(cols - 1) * minimumInteritemSpacing) / CGFloat(cols)
let cellH: CGFloat = CGFloat(arc4random_uniform(100)) + 50.0
// 找到所有列中的高度最小的Y值
let minMaxY = cellMaxYs.min()!
let minYIndex = cellMaxYs.index(of: minMaxY)!
let cellX: CGFloat = sectionInset.left + (cellW + minimumInteritemSpacing) * CGFloat(minYIndex)
let cellY: CGFloat = minMaxY + minimumLineSpacing
attr.frame = CGRect(x: cellX, y: cellY, width: cellW, height: cellH)
// 更新數(shù)組中的目前高度最低的Y值
cellMaxYs[minYIndex] = minMaxY + minimumLineSpacing + cellH

定義數(shù)組保存 attr

/// cell 對應的 UICollectionViewLayoutAttributes
fileprivate lazy var layoutAttrs: [UICollectionViewLayoutAttributes] = []

// 保存 attr
layoutAttrs.append(attr)

準備布局的最終代碼如下所示

override func prepare() {
    super.prepare()

    let cellW: CGFloat  = (collectionView!.bounds.size.width - sectionInset.left - sectionInset.right - CGFloat(cols - 1) * minimumInteritemSpacing) / CGFloat(cols)

    // 獲取 cell 的個數(shù)
    let cellCount = collectionView!.numberOfItems(inSection: 0)
    for i in 0..<cellCount {
        // 創(chuàng)建 indexPath
        let indexPath = IndexPath(item: i, section: 0)
        // 創(chuàng)建 UICollectionViewLayoutAttributes
        let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)

        let cellH: CGFloat = CGFloat(arc4random_uniform(100)) + 50.0

        // 找到所有列中的高度最小的Y值
        let minMaxY = cellMaxYs.min()!
        let minYIndex = cellMaxYs.index(of: minMaxY)!
        let cellX: CGFloat = sectionInset.left + (cellW + minimumInteritemSpacing) * CGFloat(minYIndex)
        let cellY: CGFloat = minMaxY + minimumLineSpacing
        attr.frame = CGRect(x: cellX, y: cellY, width: cellW, height: cellH)

        // 保存 attr
        layoutAttrs.append(attr)

        // 更新數(shù)組中的目前高度最低的Y值
        cellMaxYs[minYIndex] = minMaxY + minimumLineSpacing + cellH
    }
}

返回所有布局

設置好所有的 UICollectionViewLayoutAttributes 之后,要把布局信息返回給系統(tǒng),重寫 layoutAttributesForElements 方法。

/// 返回所有布局
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    return layoutAttrs
}

設置 contentSize

重寫 collectionViewContentSize 屬性來設置 collection view 的滾動范圍

// 設置 contentSize
override var collectionViewContentSize: CGSize {
    let h = cellMaxYs.max()! + sectionInset.bottom
    return CGSize(width: 0, height: h)
}

使用 HLWaterfallLayout

在 ViewController.swift 中,使用 HLWaterfallLayout 布局。

let layout = HLWaterfallLayout()

運行程序,流水布局已初步完成。

相關代碼: HLWaterfall

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容