理解 UICollection Flow Layout 流式布局

UICollectionView 因?yàn)?流式布局 (flow layout)而成了為一個非常強(qiáng)大的 UI 組件,流式布局是一種動態(tài)網(wǎng)格,提供了 table view 所不具備的功能。

flow layout 實(shí)際上是 layout 的子類。(普通的)layout 要更強(qiáng)大一點(diǎn),因?yàn)槟憧梢匀我獠季謫卧?!環(huán)形布局?沒問題!

但在本文中,我們只討論垂直的流式布局。

有兩種實(shí)現(xiàn)方式:

  1. 自定義 flow layout 對象(簡單方法)
  2. 實(shí)現(xiàn) delegate(高級方法)

單元格是如何被布局的

在討論如何實(shí)現(xiàn)上面的方法之前,先搞懂單元格是如何被布局的。

用垂直的流式布局作為例子(水平的很相似)。

  • 一行中最高的單元格決定了行高
  • 一行中所有單元格都是垂直居中對齊的
  • Minimum spacing 是單元格之間的最小距離,但實(shí)際上的間距由 collection view 的寬度決定
  • 流式布局對象會用最小間距添加單元格,直到加不下了為止,然后增加實(shí)際間距,以使它們間隔均勻
  • 每個 section 都有自己的行/單元格間距
  • 在一個 section 中,行/單元格間距是固定的;同一個 section 中不能有兩種行/單元格間距
  • 每個 section 有自己的 inset

1. 簡單方法

如果單元格具有固定的大小,只要使用 layout 對象即可——[UICollectionViewFlowLayout](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewFlowLayout_class/index.html#//apple_ref/occ/instp/UICollectionViewFlowLayout/)

這是單元格為 100x100 的例子,相隔至少 8pt,section inset 也為 8pt。

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: 100, height: 100)
    layout.minimumInteritemSpacing = 8
    layout.minimumLineSpacing = 8
    layout.headerReferenceSize = CGSize(width: 0, height: 40)
    layout.sectionInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
    collectionView.collectionViewLayout = layout
}

如果單元格很簡單,布局就這么簡單。

2. 高級方法

如果單元格有不同的尺寸,就需要用高級方法了,要實(shí)現(xiàn) [UICollectionViewDelegateFlowLayout](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewDelegateFlowLayout_protocol/#//apple_ref/occ/intfm/UICollectionViewDelegateFlowLayout/)

使用了相同的 UICollectionViewFlowLayout 對象,但會實(shí)現(xiàn)它的代理方法以定制更高級的功能。

舉個例子,如果每個單元格尺寸不同,會實(shí)現(xiàn)如下方法:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
      // 返回單元格尺寸
  }

對于最小行間距、單元格間距等等都有對應(yīng)的代理方法。

這些都是可選的,如果不實(shí)現(xiàn)它們,就會直接使用流式布局對象的屬性。

附加:如何讓單元格有固定的間距?

一個 常見問題 是要讓單元格有固定的間距。

然而,只能設(shè)置 minimumInteritemSpacing,實(shí)際單元格間距由 collection view 的寬度決定。

UICollectionViewFlowLayout 會在應(yīng)用 section inset 后排列中間的單元格,每個單元格之間的間距都相同。

如果想要固定的間距,這么做 可以實(shí)現(xiàn),通過修改 section 的左右 inset:

private let minItemSpacing: CGFloat = 8
private let itemWidth: CGFloat      = 100
private let headerHeight: CGFloat   = 32

override func viewDidLayoutSubviews() {
      super.viewDidLayoutSubviews()

      // 創(chuàng)建自定義流式布局,將單元格均勻分布,并將它們放在中間
      let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: itemWidth, height: itemWidth)
    layout.minimumInteritemSpacing = minItemSpacing
    layout.minimumLineSpacing = minItemSpacing
    layout.headerReferenceSize = CGSize(width: 0, height: headerHeight)

      // 求 n,n 是 collection view 可以容納的單元格數(shù)量
      var n: CGFloat = 1
    let containerWidth = collectionView.bounds.width
    while true {
        let nextN = n + 1
        let totalWidth = (nextN*itemWidth) + (nextN-1)*minItemSpacing
        if totalWidth > containerWidth {
            break
        } else {
            n = nextN
        }
    }

      // 計算 section 的左右 inset
      // 設(shè)置 section 的 inset 會影響單元格,以將它們水平居中對齊
      let inset = max(minItemSpacing, floor( (containerWidth - (n*itemWidth) - (n-1)*minItemSpacing) / 2 ) )
    layout.sectionInset = UIEdgeInsets(top: minItemSpacing, left: inset, bottom: minItemSpacing, right: inset)

    collectionView.collectionViewLayout = layout
}

附加:自定義布局

流式布局是開箱即用的。易于使用,對于大多數(shù) UI 都足夠了。

但也可以創(chuàng)建自己的 自定義布局 。

布局類的核心方法是 layoutAttributesForElementsInRect:??梢宰x一下 來自 objc.io 的教程,寫的很好。這是更高級別的主題。

注意:通常我們會使用 autolayout 約束,但對于單元格(cell),需要用傳統(tǒng)的方式設(shè)置 frame。只有 cell 要這么做。單元格里面的視圖仍然可以使用自動布局。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容