一、前言
我面過(guò)許多 iOS 開(kāi)發(fā)者,其中有個(gè)問(wèn)題,我偶爾會(huì)問(wèn)到(如果與候選者聊的愉快,我會(huì)問(wèn)這個(gè)小問(wèn)題,回答的不好不會(huì)影響最終的結(jié)果,回答的好可以加分):
大家可能或多或少有點(diǎn)了解:UITableView 能干的事,UICollectionView 幾乎都能干(總有干不了的,除非自定義,例如:刪除手勢(shì)),那么,你認(rèn)為 UITableView 與 UICollectionView 分別應(yīng)該在哪些場(chǎng)景用到?
但實(shí)際上,并沒(méi)有標(biāo)準(zhǔn)的答案,而我也只有個(gè)人建議給出,主要取決于需求(嗯,我也知道是廢話):
- 如果是瀑布流,那么,老老實(shí)實(shí)的使用 UICollectionView;
- 如果是簡(jiǎn)單的單行列表,那么首選肯定是 UITableView;
總的來(lái)說(shuō)就是:?jiǎn)瘟杏?UITableView,多列或復(fù)雜邏輯就用 UICollectionView;
那啥,有人會(huì)和我扛,UITableView 單行也能多列!嗯,那你就違背了『用合適的控件使合適的事情,即復(fù)雜問(wèn)題簡(jiǎn)單化』。
簡(jiǎn)單看一下,兩者的 UI 區(qū)別:

52f43bb813ff4dbf9f1827fe04976df0_tplv-k3u1fbpfcp-watermark.png
從上圖可以看出:
- UITableView 默認(rèn)含有表頭、表尾;而 UICollectionView 默認(rèn)沒(méi)有;
- UITableView 默認(rèn)是 Section + (Header + UITableViewCell + Footer);
- UICollectionView 默認(rèn)是 Section +(Header + UICollectionViewCell + Footer);
二、UICollectionView 官方流程圖

2.png
上圖是官方的流程圖:
- 最上面是一個(gè) N行M列 的 Cell 單元格,我們叫作: UICollectionViewCell;
- 類(lèi)似 UITableView,UICollectionView 是 UICollectionViewCell 的容器;
- UICollectionViewCell 的展示方式是由左側(cè)的 layout(attributes) 來(lái)決定,官方提供了一個(gè)已經(jīng)定義好的 layout:UICollectionViewFlowLayout (流式布局),我們也可以自定義;
- 同樣,類(lèi)似 UITableView,cell 的數(shù)據(jù)來(lái)源由 dataSource 負(fù)責(zé),cell 的操作由 delegate 負(fù)責(zé);
對(duì)于 layout ,你可以類(lèi)比 delegate 或者 data source,即 cell 的展示由 layout 來(lái)決定。
三、UICollectionViewFlowLayout 核心屬性源碼分析
@available(iOS 6.0, *)
open class UICollectionViewFlowLayout : UICollectionViewLayout {
// 同一組當(dāng)中:
// 垂直方向:行與行之間的間距 or 水平方向:列與列之間的間距
open var minimumLineSpacing: CGFloat
// 垂直方向:同一行中,cell之間的間距 or 水平方向:同一列中,cell與cell之間的間距
open var minimumInteritemSpacing: CGFloat
// 每個(gè) cell 的尺寸
open var itemSize: CGSize
// 滑動(dòng)方向:默認(rèn)滑動(dòng)方向是垂直方向滑動(dòng)
open var scrollDirection: UICollectionView.ScrollDirection
// 每一組頭視圖的尺寸。如果是垂直方向滑動(dòng),則只有高起作用 or 如果是水平方向滑動(dòng),則只有寬起作用
open var headerReferenceSize: CGSize
// 同上,作用于尾視圖
open var footerReferenceSize: CGSize
// 每一組的內(nèi)容縮進(jìn)
open var sectionInset: UIEdgeInsets
// 下面兩屬性,類(lèi)似 UITableView,就是我們常說(shuō)的,是否:吸頂 / 吸底
@available(iOS 9.0, *)
open var sectionHeadersPinToVisibleBounds: Bool
@available(iOS 9.0, *)
open var sectionFootersPinToVisibleBounds: Bool
}
上面的源碼注釋已經(jīng)寫(xiě)的很詳細(xì)了,后續(xù)的組件開(kāi)發(fā)中會(huì)用到,這里大家只需要了解即可。
四、UICollectionView 源碼簡(jiǎn)要分析
@available(iOS 6.0, *)
open class UICollectionView : UIScrollView, UIDataSourceTranslating {
// 給定 frame 大小 && layout(布局)
public init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)
......
// 布局(可以使用默認(rèn)的 UICollectionViewFlowLayout,也可以自定義)
open var collectionViewLayout: UICollectionViewLayout
// 委托:操作 UICollectionViewCell(item)
weak open var delegate: UICollectionViewDelegate?
// 數(shù)據(jù)源
weak open var dataSource: UICollectionViewDataSource?
// 注冊(cè) cell 的不同方式:手寫(xiě)代碼 or XIB
open func register(_ cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String)
open func register(_ nib: UINib?, forCellWithReuseIdentifier identifier: String)
// 同上,注冊(cè) header / footer 的不同方式
open func register(_ viewClass: AnyClass?, forSupplementaryViewOfKind elementKind: String, withReuseIdentifier identifier: String)
open func register(_ nib: UINib?, forSupplementaryViewOfKind kind: String, withReuseIdentifier identifier: String)
// 重用 cell、header / footer(根據(jù)傳入不同的 identifier)
open func dequeueReusableCell(withReuseIdentifier identifier: String, for indexPath: IndexPath) -> UICollectionViewCell
open func dequeueReusableSupplementaryView(ofKind elementKind: String, withReuseIdentifier identifier: String, for indexPath: IndexPath) -> UICollectionReusableView
}
五、UICollectionViewDataSource 源碼必要方法分析
public protocol UICollectionViewDataSource : NSObjectProtocol {
// 我們可能有多個(gè) Section,每個(gè) Section 又可以有不同個(gè)數(shù)的 Items,即我們的數(shù)據(jù)結(jié)構(gòu)是二維:[Section][Item]
@available(iOS 6.0, *)
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
// 必需調(diào)用 -dequeueReusableCellWithReuseIdentifier:forIndexPath: 來(lái)獲取可重用的 cell,
// 然后根據(jù) indexPath 來(lái)重新賦值,更新顯示,最后返回該 cell
@available(iOS 6.0, *)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
// 可選方法,如果只有 1 個(gè) section,可以不用實(shí)現(xiàn)(官方文檔:默認(rèn)返回值是 1)
@available(iOS 6.0, *)
optional func numberOfSections(in collectionView: UICollectionView) -> Int
// 可選方法,類(lèi)同上面第二個(gè)方法
// 必需調(diào)用 -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: 來(lái)獲取可重用的 header / footer
@available(iOS 6.0, *)
optional func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
}