UICollectionView
因?yàn)?流式布局 (flow layout)而成了為一個(gè)非常強(qiáng)大的 UI 組件,流式布局是一種動(dòng)態(tài)網(wǎng)格鲁纠,提供了 table view 所不具備的功能。
flow layout 實(shí)際上是 layout 的子類。(普通的)layout 要更強(qiáng)大一點(diǎn),因?yàn)槟憧梢匀我獠季謫卧窈舱。…h(huán)形布局?沒問題讽坏!
但在本文中锭魔,我們只討論垂直的流式布局。
有兩種實(shí)現(xiàn)方式:
- 自定義 flow layout 對象(簡單方法)
- 實(shí)現(xiàn) delegate(高級方法)
單元格是如何被布局的
在討論如何實(shí)現(xiàn)上面的方法之前路呜,先搞懂單元格是如何被布局的迷捧。
用垂直的流式布局作為例子(水平的很相似)织咧。
- 一行中最高的單元格決定了行高
- 一行中所有單元格都是垂直居中對齊的
- Minimum spacing 是單元格之間的最小距離,但實(shí)際上的間距由 collection view 的寬度決定
- 流式布局對象會(huì)用最小間距添加單元格漠秋,直到加不下了為止笙蒙,然后增加實(shí)際間距,以使它們間隔均勻
- 每個(gè) section 都有自己的行/單元格間距
- 在一個(gè) section 中庆锦,行/單元格間距是固定的捅位;同一個(gè) section 中不能有兩種行/單元格間距
- 每個(gè) 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
對象右莱,但會(huì)實(shí)現(xiàn)它的代理方法以定制更高級的功能蚜锨。
舉個(gè)例子,如果每個(gè)單元格尺寸不同慢蜓,會(huì)實(shí)現(xiàn)如下方法:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
// 返回單元格尺寸
}
對于最小行間距亚再、單元格間距等等都有對應(yīng)的代理方法。
這些都是可選的晨抡,如果不實(shí)現(xiàn)它們氛悬,就會(huì)直接使用流式布局對象的屬性。
附加:如何讓單元格有固定的間距耘柱?
一個(gè) 常見問題 是要讓單元格有固定的間距如捅。
然而,只能設(shè)置 minimumInteritemSpacing
调煎,實(shí)際單元格間距由 collection view 的寬度決定镜遣。
UICollectionViewFlowLayout
會(huì)在應(yīng)用 section inset 后排列中間的單元格,每個(gè)單元格之間的間距都相同士袄。
如果想要固定的間距悲关,這么做 可以實(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
}
}
// 計(jì)算 section 的左右 inset
// 設(shè)置 section 的 inset 會(huì)影響單元格西土,以將它們水平居中對齊
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:
般甲。可以讀一下 來自 objc.io 的教程鹅颊,寫的很好敷存。這是更高級別的主題。
注意:通常我們會(huì)使用 autolayout 約束堪伍,但對于單元格(cell)锚烦,需要用傳統(tǒng)的方式設(shè)置 frame。只有 cell 要這么做帝雇。單元格里面的視圖仍然可以使用自動(dòng)布局涮俄。