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)方式:
- 自定義 flow layout 對象(簡單方法)
- 實(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 要這么做。單元格里面的視圖仍然可以使用自動布局。