最近將 UICollectionView 進(jìn)行了一個(gè)全面的學(xué)習(xí)及總結(jié)楷拳,參考了網(wǎng)上大量的文章担巩,把官方文檔進(jìn)行了大概翻譯,最后有個(gè)小Demo换棚。
UICollectionView基礎(chǔ)知識(shí)
collection view 是運(yùn)用一個(gè)靈活多變的布局呈現(xiàn)一系列有序數(shù)據(jù)項(xiàng)的一種方法式镐。collection view最通常的使用使用像網(wǎng)格狀排列來(lái)呈現(xiàn)數(shù)據(jù)項(xiàng),但是 iOS 的 collection view 的能力不僅限于行和列固蚤。使用 collection views, 視覺元素的精確布局可通過子類化定義并被動(dòng)態(tài)改變娘汞。所以你可以實(shí)現(xiàn)網(wǎng)格,棧颇蜡,圓形布局价说,動(dòng)態(tài)改變布局辆亏,或任何你可以想象的排列布局。
Collection View 是由多個(gè)對(duì)象協(xié)作而成
在上面可以看出鳖目,一個(gè) CollectionView 視圖從 data source 中獲取數(shù)據(jù)信息扮叨,data source 和 delegate 來(lái)管理具體的單元對(duì)象,包括選中领迈、未選中彻磁、高亮、未高亮等狀態(tài)狸捅。Layout 決定了每個(gè) cell 之間的邊界及布局信息衷蜓,并通過 layoutattributes 來(lái)決定每個(gè) cell 的屬性。
ReusableView 采用的可重用 cell 的方法尘喝,可以提高效率,支持三種不同的可重用的view
- cell 展示了 collection view 的主要內(nèi)容磁浇,cell 的內(nèi)容來(lái)自你的數(shù)據(jù)對(duì)象,每個(gè) cell 必須是
UICollectionViewCell
類的實(shí)例朽褪。cell 對(duì)象可以管理自己的選中和高亮狀態(tài) - Supplementary views 展示了關(guān)于 section 的信息置吓,同樣也是數(shù)據(jù)驅(qū)動(dòng)的,是可選的缔赠。
- Decoration views 裝飾視圖衍锚,不與數(shù)據(jù)相關(guān),完全屬于 layout 對(duì)象嗤堰。
Layout 對(duì)象控制視覺顯示
layout object 決定著 cell 的大小戴质,位置還有其他顯示相關(guān)的屬性。
layout object 沒有接觸任何視圖踢匣,它只是一個(gè)屬性集合告匠。而沒有任何限制,你可以隨你所想符糊。
layout object 不止可以控制視圖的寬高尺寸凫海,還可以控制視圖之間的關(guān)系屬性,透明度男娄,3D行贪,和他跟其他視圖之間的可見性對(duì)比。
Collection Views Initiate Animations Automatically
collection views 在 cell 移動(dòng)模闲、插入或刪除的時(shí)候建瘫,會(huì)有默認(rèn)的動(dòng)畫∈郏可以自定義這些動(dòng)畫啰脚。
設(shè)計(jì) Data Source 和 Delegate
每個(gè) collection view 必須有一個(gè) data source 的對(duì)象,是 app 展示的內(nèi)容。必須提供 collection view 需要的信息橄浓,比如多少個(gè) items 等粒梦。delegate 對(duì)象一般用于和內(nèi)容的交互相關(guān)的≥┦担可以管理 cell 的高亮選中狀態(tài)匀们,除此之外還有額外的工作,比如自定義布局的 cell 的大小及間隔 spacing 等准给。
用 Data Source 管理你的內(nèi)容
data source 需要遵循 UICollectionViewDataSource
協(xié)議提供三個(gè)信息:
- collection view 包括多少個(gè) sections
- 每個(gè) section 包括多少個(gè) items
- 每個(gè) cell 展示的內(nèi)容
幾個(gè)方法
- 展示數(shù)量
collectionView(_:numberOfItemsInSection:)
numberOfSectionsInCollectionView(_:)
- 展示 cell
collectionView(_:cellForItemAtIndexPath:)
collectionView(_:viewForSupplementaryElementOfKind:atIndexPath:)
- cell 重排
collectionView(_:canMoveItemAtIndexPath:)
collectionView(_:moveItemAtIndexPath:toIndexPath:)
設(shè)計(jì)你的 Data Objects
一個(gè)有效的數(shù)據(jù)源使用節(jié)和元素來(lái)組織基礎(chǔ)的數(shù)據(jù)對(duì)象泄朴。一個(gè)簡(jiǎn)單的解決方案就是如圖一樣,把數(shù)據(jù)源組織成一個(gè)嵌套數(shù)組露氮,最外層數(shù)據(jù)包含一個(gè)或多個(gè)數(shù)據(jù)展示數(shù)據(jù)源中的節(jié)祖灰,每個(gè)節(jié)數(shù)據(jù)包含多個(gè)元素。當(dāng)設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)時(shí)畔规,要考慮性能問題局扶,集合視圖訪問數(shù)據(jù)源僅是為了計(jì)算一共多少元素并獲取當(dāng)前屏幕所需的數(shù)據(jù)對(duì)象。如果布局對(duì)象只是依賴于數(shù)據(jù)對(duì)象叁扫,當(dāng)數(shù)據(jù)對(duì)象包含上千條數(shù)據(jù)的時(shí)候详民,性能會(huì)受到大幅的影響。
告訴集合視圖相關(guān)的數(shù)據(jù)源信息
當(dāng)以下發(fā)生時(shí)陌兑,需要提供給集合視圖相關(guān) data source
- collection view 第一次顯示的時(shí)候
- 給 collection view 指定了不同的 data source
- 明確調(diào)用了
reloadData
方法 - collection view 的代理執(zhí)行了
performBatchUpdates:completion:
方法,或者任何移動(dòng)、插入由捎、刪除方法執(zhí)行的時(shí)候
collectionView.performBatchUpdates({ () -> Void in
collectionView.insertItemsAtIndexPaths(insertIndexPaths)
collectionView.moveItemAtIndexPath(currentIndexPath, toIndexPath: toIndexPath)
}, completion: { (isFinish) -> Void in
})
通過numberOfSectionsInCollectionView:
來(lái)提供分組數(shù)量兔综,可選的方法,默認(rèn)是1狞玛,使用collectionView:numberOfItemsInSection:
來(lái)提供每個(gè)分組的 items 的數(shù)量软驰。
配置單元格(cells)和增補(bǔ)視圖(supplementa views)
要給單元提供內(nèi)容,需要做的兩件事:
- 在 storyboard 文件中嵌入模板單元心肪,(可以注冊(cè)一個(gè) class 或者 nib 文件)
- 在數(shù)據(jù)源中锭亏,重用出列(dequeue)并且配置相應(yīng)的 cell 單元
復(fù)用標(biāo)識(shí)(Reuse identifiers)可以讓注冊(cè)多種類型的單元和增補(bǔ)視圖成為可能。當(dāng)請(qǐng)求一個(gè)視圖或一個(gè)對(duì)象時(shí)硬鞍,可以使用提供的索引路徑?jīng)Q定想要使用哪種類型的視圖和單元格慧瘤,然后傳遞適當(dāng)?shù)闹赜脴?biāo)識(shí)給 dequeue 函數(shù)。
注冊(cè)單元格和增補(bǔ)視圖
兩種方式注冊(cè)單元格固该,在 storyboard 中注冊(cè)或者手動(dòng)代碼注冊(cè)锅减。
- 使用 storyboard 文件,拖拽 cell 或者 supplementary views 到 storyboard 中伐坏,并且配置相關(guān)屬性怔匣,設(shè)置重用標(biāo)識(shí)。
- 使用手動(dòng)代碼的方式注冊(cè)
- 注冊(cè) cell桦沉,采用
registerClass:forCellWithReuseIdentifier:
或者registerNib:forCellWithReuseIdentifier:
方法來(lái)注冊(cè) cell - 注冊(cè) supplementary views每瞒,采用
registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
或者registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
方法來(lái)注冊(cè)
- 注冊(cè) cell桦沉,采用
supplementary views 除了重用標(biāo)識(shí)外金闽,還有一個(gè)額外的標(biāo)識(shí),每個(gè) layout 對(duì)象負(fù)責(zé)定義 supplementary view 支持的 kind 剿骨。比如 UICollectionViewFlowLayout
類支持兩種類型 UICollectionElementKindSectionHeader
和 UICollectionElementKindSectionFooter
注意:如果使用的自定義的 layouts,需要自己定義 supplementary views 的類型代芜。
重用和配置單元格和增補(bǔ)視圖
data source 對(duì)象負(fù)責(zé)提供 cell 和 supplementary view 的內(nèi)容,包含兩個(gè)方法: collectionView:cellForItemAtIndexPath:
和 collectionView:viewForSupplementaryElementOfKind:atIndexPath:
,實(shí)現(xiàn)兩個(gè)方法的簡(jiǎn)單步驟:
- 重用 cell 或 supplementary views
dequeueReusableCellWithReuseIdentifier:forIndexPath:
dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
- 對(duì)于指定的
IndexPath
利用數(shù)據(jù)來(lái)配置 cell 或 view - 返回 view 或 cell
例子:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let newCell = collectionView.dequeueReusableCellWithReuseIdentifier(MyCellID, forIndexPath: indexPath) as! MyCustomCell
newCell.cellLabel.text = "Section:\(indexPath.section), Item:\(indexPath.item)"
return newCell
}
插入懦砂、刪除蜒犯、移動(dòng) sections 和 items
插入、刪除荞膘,移動(dòng)單個(gè) section 或者 items罚随,必須遵循兩個(gè)步驟:
- 在 datasource 對(duì)象中更新數(shù)據(jù)
- 調(diào)用相應(yīng)的插入、刪除等方法
當(dāng)插入羽资、刪除和移動(dòng)單個(gè) item 的時(shí)候淘菩,collection view 的方法會(huì)對(duì)這些變化自動(dòng)產(chǎn)生一個(gè)動(dòng)畫效果。如果想多個(gè)改變共用一個(gè)動(dòng)畫屠升,必須在performBatchUpdates:completion:
方法的閉包里執(zhí)行插入潮改、刪除、移動(dòng)等腹暖。
例子:
collectionView.performBatchUpdates({
let itemPaths = collectionView.indexPathsForSelectedItems()
//從 data source 中刪除數(shù)據(jù)
deleteItemFromDataSourceAtIndexPaths()
collectionView.deleteItemsAtIndexPaths(itemPaths)
}, completion: nil)
管理 cell 的選中汇在、高亮狀態(tài)
collection view 支持單個(gè) item 選中,也可以配置為多個(gè) item 選中或者禁止選中脏答。當(dāng) selectedBackgroundView
中包含一個(gè)有效的 view 的時(shí)候糕殉,當(dāng) cell 是高亮或選中狀態(tài)時(shí)會(huì)顯示這個(gè) view。此種方式可以改變 cell 高亮或選中時(shí)的背景顏色殖告。
collection views 的代理提供的一些方法:
collectionView:shouldSelectItemAtIndexPath:
collectionView:shouldDeselectItemAtIndexPath:
collectionView:didSelectItemAtIndexPath:
collectionView:didDeselectItemAtIndexPath:
collectionView:shouldHighlightItemAtIndexPath:
collectionView:didHighlightItemAtIndexPath:
collectionView:didUnhighlightItemAtIndexPath:
改變 cell 背景顏色的一個(gè)例子
- (void)collectionView:(UICollectionView *)colView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell* cell = [colView cellForItemAtIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor blueColor];
}
- (void)collectionView:(UICollectionView *)colView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell* cell = [colView cellForItemAtIndexPath:indexPath];
cell.contentView.backgroundColor = nil;
}
高亮和選中區(qū)別
顯示編輯按鈕
當(dāng)在 cell 中執(zhí)行一個(gè)長(zhǎng)按的手勢(shì)阿蝶,collection view 可以顯示一個(gè)編輯按鈕,可以用于剪切黄绩、粘貼羡洁、復(fù)制這個(gè) cell。編輯按鈕能被顯示必須滿足以下條件:
- 代理必須實(shí)現(xiàn)三個(gè)方法
-
collectionView:shouldShowMenuForItemAtIndexPath:
是否顯示菜單 -
collectionView:canPerformAction:forItemAtIndexPath:withSender:
菜單中哪些編輯操作可以顯示 -
collectionView:performAction:forItemAtIndexPath:withSender:
對(duì)于顯示的編輯操作怎么執(zhí)行
-
- 支持三種編輯操作
cut:
copy:
paste:
layouts 布局之間的轉(zhuǎn)場(chǎng)
最簡(jiǎn)單的在兩個(gè)布局之間轉(zhuǎn)變的方法是 setCollectionViewLayout:animated:
爽丹。如果需要控制轉(zhuǎn)場(chǎng)動(dòng)畫或者制作可交互的轉(zhuǎn)場(chǎng)動(dòng)畫筑煮,需要使用 UICollectionViewTransitionLayout
對(duì)象。
使用 UICollectionViewTransitionLayout
對(duì)象的步驟:
- 使用
initWithCurrentLayout:nextLayout:
方法創(chuàng)建類 - 周期性的修改
transitionProgress
屬性的值习劫,在改變過渡的進(jìn)度后咆瘟,使用 collection view 的 `` 方法使布局無(wú)效。 -
collectionView:transitionLayoutForOldLayout:newLayout:
代理方法诽里,返回過渡的布局對(duì)象 - 選擇性的使用
updateValue:forAnimatedKey:
方法修改布局對(duì)象的值袒餐,穩(wěn)定值為0
使用流水布局 Flow Layout
配置 flow layout 的幾個(gè)步驟:
- 生成一個(gè) flow layout對(duì)象并且指定給相應(yīng)的 collection view
- 配置 cells 的寬度和高度
- 設(shè)置行間隔和單元間隔
- 設(shè)置 headers 和 footers 的大小
- 設(shè)置滾動(dòng)方向
自定義屬性
指定 item 的大小
對(duì)于所有的 items 可以使用屬性 itemSize
來(lái)設(shè)置大小。如果設(shè)定不同的 itemSize,需要實(shí)現(xiàn) collectionView:layout:sizeForItemAtIndexPath:
方法
指定 items 和 lines 之間的間隔
注意此處設(shè)置的是最小間隔灸眼,實(shí)際間隔可能大小此間隔卧檐。可以通過屬性設(shè)置 minimumLineSpacing
和 minimumInteritemSpacing
或者使用代理方法來(lái)實(shí)現(xiàn) collectionView:layout:minimumLineSpacingForSectionAtIndex:
和 collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
使用內(nèi)邊距設(shè)置邊緣
使用 Flow Layout 的子類
可以自己生成一個(gè) flowlayout 的子類焰宣,然后更改其中的屬性方法等實(shí)現(xiàn)自定義霉囚。
生成自定義的布局 Layout
實(shí)現(xiàn)自定義的布局不難,最難的如何在布局中計(jì)算 items 位置匕积。
實(shí)現(xiàn) UICollectionViewLayout 子類
繼承 UICollectionViewLayout 需要完成的重要的內(nèi)容:
- 指定可滾動(dòng)內(nèi)容區(qū)域的大小 size
- 提供每個(gè) cell 或 view 的layout屬性盈罐,以便 collection view 可以展示這些 cells
理解 Layout 的布局過程
invalidateLayout
會(huì)使當(dāng)前的 layout 無(wú)效,并觸發(fā) layout 的更新闪唆,會(huì)強(qiáng)迫 layout 對(duì)象重新計(jì)算 layout 屬性盅粪。與 reloadData
不一樣,當(dāng) data source 中的數(shù)據(jù)發(fā)生改變悄蕾,適合用 reloadData
方法票顾。
在布局過程中,會(huì)按順序調(diào)用一下函數(shù)帆调,可以在這些方法中計(jì)算 item 的位置信息奠骄。
-
prepareLayout
方法來(lái)執(zhí)行一些準(zhǔn)備工作,可以進(jìn)行一些 layout 布局需要的計(jì)算等 -
collectionViewContentSize
方法用來(lái)返回整個(gè)內(nèi)容的 content size 大小 -
layoutAttributesForElementsInRect:
方法用來(lái)返回在指定的矩形區(qū)域內(nèi)的 cells 的屬性等信息
prepareLayout
是專門用來(lái)準(zhǔn)備布局的番刊,在prepareLayout
方法里面我們可以事先就計(jì)算后面要用到的布局信息并存儲(chǔ)起來(lái)含鳞,防止后面方法多次計(jì)算,提高性能芹务。例如民晒,我們可以在此方法就計(jì)算好每個(gè) cell 的屬性、整個(gè) CollectionView 的內(nèi)容尺寸等等锄禽。此方法在布局之前會(huì)調(diào)用一次,之后只有在調(diào)用invalidateLayout
靴姿、shouldInvalidateLayoutForBoundsChange:
返回YES和 UICollectionView 刷新的時(shí)候才會(huì)調(diào)用沃但。
prepareLayout
方法是為確定布局中各 cell 和 view 位置做計(jì)算,需要在此方法中算出足夠的信息以供后續(xù)方法計(jì)算內(nèi)容區(qū)域的整體 size佛吓,collection view 使用 content size 以正確地配置 scroll view宵晚。比如 content size 長(zhǎng)寬均超過屏幕的話,水平與豎直方向的滾動(dòng)都會(huì)被 enable维雇∮偃校基于當(dāng)前滾動(dòng)位置,collection view 會(huì)調(diào)用 layoutAttributesForElementsInRect:
方法以請(qǐng)求特定 rect (有可能是也可能不是可見 rect)中 cell 和 view 的屬性吱型。到此逸贾,core layout process 已經(jīng)結(jié)束了。
layout 結(jié)束之后,cells 和views 的屬性在你或者 collection view invalidate布局之前都不會(huì)變铝侵。調(diào)用 invalidateLayout
方法會(huì)導(dǎo)致新的一次 layout process 開始灼伤,以調(diào)用 prepareLayout
方法開始。collection view 可以在滾動(dòng)的過程中自動(dòng) invalidate 布局咪鲜,用戶滾動(dòng)內(nèi)容過程中狐赡,collection view調(diào)用layout 的 shouldInvalidateLayoutForBoundsChange:
方法,如果返回值為 YES 則 invalidate 布局疟丙。(但需要知道的是颖侄,invalidateLayout 并不會(huì)馬上觸發(fā) layout update process,而是在下一個(gè)view更新周期中,collection view 發(fā)現(xiàn) layout 已經(jīng) dirty 才會(huì)去更新)
享郊。
創(chuàng)建布局屬性 Layout Attributes
自定義 layout 需要返回一個(gè) UICollectionViewLayoutAttributes
類的對(duì)象览祖,這些對(duì)象可以在很多不同的方法中創(chuàng)建,但創(chuàng)建時(shí)間可以根據(jù)具體情況決定拂蝎。如果 collection view 不會(huì)處理上千個(gè) items 時(shí)穴墅,則 prepareLayout
創(chuàng)建會(huì)比用戶滾動(dòng)過程中用到時(shí)在計(jì)算更高效,因?yàn)閯?chuàng)建的屬性可以緩存起來(lái)温自。如果計(jì)算所有屬性并緩存起來(lái)所帶來(lái)的性能消耗比請(qǐng)求時(shí)在計(jì)算屬性的消耗更大玄货,則可以在請(qǐng)求的時(shí)候在計(jì)算相關(guān)屬性。
UICollectionViewLayoutAttributes
的屬性:
- frame
- bounds
- center
- size
- transform3D
- transform
- alpha
- zIndex
- hidden
創(chuàng)建 UICollectionViewLayoutAttributes
類對(duì)象時(shí)悼泌,可以使用一些方法:
init(forCellWithIndexPath indexPath: NSIndexPath)
init(forSupplementaryViewOfKind:withIndexPath:)
init(forDecorationViewOfKind:withIndexPath:)
view 的類型不同松捉,必須使用正確的類方法,因?yàn)?collection view 會(huì)根據(jù)這些信息向 data source 對(duì)象請(qǐng)求適當(dāng)類型的 view馆里,使用錯(cuò)誤的方法在錯(cuò)誤的地方創(chuàng)建錯(cuò)誤的 view隘世。
創(chuàng)建每個(gè)屬性對(duì)象后,要將相應(yīng)的 view 的相關(guān)屬性設(shè)置上鸠踪。最基本的要設(shè)置 view 的 size 和 position 信息丙者。如果布局中有 view 重疊了,需要配置正確的 zIndex
屬性來(lái)維持有序的狀態(tài)营密。其他屬性可以控制 cell 或 view 的外觀及可見性械媒。
準(zhǔn)備 Layout
在一個(gè)布局周期中,首先會(huì)調(diào)用 prepareLayout
方法评汰,可以來(lái)執(zhí)行一些準(zhǔn)備工作纷捞,可以進(jìn)行一些 layout 布局需要的計(jì)算等,可以存儲(chǔ)一些 layout attributes 信息被去。
給定矩形中的 items 布局屬性
layout process 的最后主儡,collection view 會(huì)調(diào)用 layoutAttributesForElementsInRect:
方法,對(duì)于一個(gè)大的可滾動(dòng)內(nèi)容區(qū)域惨缆,collection view 可能只會(huì)請(qǐng)求當(dāng)前可見的那部分區(qū)域中的所有 items 屬性糜值。這個(gè)方法支持獲取任意 rect 中 items 的信息丰捷,因?yàn)橛锌赡茉诓迦爰皠h除時(shí)做動(dòng)畫效果。
layoutAttributesForElementsInRect:
方法實(shí)現(xiàn)需要尊重如下的步驟:
- 遍歷
prepareLayout
方法產(chǎn)生的數(shù)據(jù)以訪問緩存的屬性或創(chuàng)建新的屬性 - 檢查每個(gè) item 中的 frame 以確定是否與
layoutAttributesForElementsInRect:
方法中指定的 rect 有重疊部分 - 對(duì)每個(gè)重疊的 item 臀玄,添加一個(gè)對(duì)應(yīng)的
UICollectionViewLayoutAttributes
對(duì)象到一個(gè)數(shù)組中 - 返回布局屬性的數(shù)組給 collection view
不僅要記住緩存layout信息能夠帶來(lái)性能提升瓢阴,也要記住不斷重復(fù)為cells創(chuàng)建新layout屬性的計(jì)算代價(jià)是十分昂貴的,足以影響到app的性能健无。當(dāng)collection view管理的items量很大時(shí)荣恐,采用在請(qǐng)求時(shí)創(chuàng)建layout屬性的方式是十分合理的。
按需提供布局屬性
collection view 會(huì)在正常的 layout 過程之外周期性的讓你提供單個(gè) items 的layout 對(duì)象累贤。比如為某 item 配置插入和刪除對(duì)話叠穆。通過以下方法提供信息:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath:
layoutAttributesForDecorationViewOfKind:atIndexPath:
layoutAttributesForItemAtIndexPath:
所有自定義的 layout 必須重寫的方法。
當(dāng)返回屬性時(shí)臼膏,不應(yīng)該更新這些 layout 屬性硼被,如果需要改變 layout 信息,調(diào)用 invalidateLayout
在接下來(lái)的 layout 周期中更新這些信息渗磅。
兩種方式設(shè)置 collection view 的 layout 為自定義的 layout嚷硫,
- 一種方式在 storyboard 文件中,在 Attributes inspector 中設(shè)置 Layout 從 Flow 改為 Custom始鱼。
- 另外一種直接代碼設(shè)置
self.collectionView.collectionViewLayout = [[MyCustomLayout alloc] init];
讓你的 Layout 更優(yōu)異
除了上述必須實(shí)現(xiàn)的方法仔掸,還有一些特性能夠改善自定義的 layout 的用戶體驗(yàn),實(shí)現(xiàn)這些屬性是可選但是推薦實(shí)現(xiàn)的医清。
通過附加 view 提供內(nèi)容品質(zhì)
supplementary views 與 cells 分離并且有自己的 layout 屬性起暮,由 data source 提供,其目的是為 app 主要內(nèi)容增強(qiáng)信息会烙。UICollectionViewFlowLayout 中使用 supplementary view 來(lái)作為 section headers 和 footers负懦,除此之外,可以使用 supplementary views 給每個(gè) cell 提供一個(gè)自己的 label柏腻,用于顯示 cell 的信息纸厉。與 cells 一樣,supplementary views 也需要重用五嫂,所有 supplementary views 需要繼承自 UICollectionReusableView
類残腌。
添加 supplementary views 到 layout 的過程如下:
- 注冊(cè) supplementary views 到 layout 對(duì)象中,
registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
或者registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
- 在 data source 中實(shí)現(xiàn)
collectionView:viewForSupplementaryElementOfKind:atIndexPath:
方法贫导,由于這些 view 是可重用的,調(diào)用dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
來(lái)獲得 view - 像為 cells 創(chuàng)建屬性一樣蟆盹,為 supplementary views 創(chuàng)建 layout 屬性
- 在
layoutAttributesForElementsInRect:
方法中返回的屬性數(shù)組中包含 supplementary views 的 layout 屬性 - 如果需要的話孩灯,實(shí)現(xiàn)
layoutAttributesForSupplementaryViewOfKind:atIndexPath:
方法為特定的 supplementary views 返回屬性對(duì)象
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *layoutAttributes = [NSMutableArray array];
// Cells
// We call a custom helper method -indexPathsOfItemsInRect: here
// which computes the index paths of the cells that should be included
// in rect.
NSArray *visibleIndexPaths = [self indexPathsOfItemsInRect:rect];
for (NSIndexPath *indexPath in visibleIndexPaths) {
UICollectionViewLayoutAttributes *attributes =
[self layoutAttributesForItemAtIndexPath:indexPath];
[layoutAttributes addObject:attributes];
}
// Supplementary views
NSArray *dayHeaderViewIndexPaths = [self indexPathsOfDayHeaderViewsInRect:rect];
for (NSIndexPath *indexPath in dayHeaderViewIndexPaths) {
UICollectionViewLayoutAttributes *attributes =
[self layoutAttributesForSupplementaryViewOfKind:@"DayHeaderView"
atIndexPath:indexPath];
[layoutAttributes addObject:attributes];
}
NSArray *hourHeaderViewIndexPaths = [self indexPathsOfHourHeaderViewsInRect:rect];
for (NSIndexPath *indexPath in hourHeaderViewIndexPaths) {
UICollectionViewLayoutAttributes *attributes =
[self layoutAttributesForSupplementaryViewOfKind:@"HourHeaderView"
atIndexPath:indexPath];
[layoutAttributes addObject:attributes];
}
return layoutAttributes;
}
處理 supplementary views 布局屬性的過程和 cell 屬性過程是一樣的,但是不同的是 supplementary views 可以有很多種但只有一種 cell逾滥。這是因?yàn)?supplementary view 與它們是分離開的峰档,是為了烘托主旨败匹,所以每個(gè) supplementary view 方法都會(huì)指明其類別 kind 以方便正確計(jì)算其特有的屬性。
在 layout 中添加 Decoration Views
Decoration views 是 Layout UI 特征的有效點(diǎn)綴讥巡,它僅僅提供一些可視化的內(nèi)容掀亩,與 data source 無(wú)關(guān)』肚辏可以用來(lái)自定義背景槽棍,在 cells 縫隙之間填充,甚至可以掩蓋 cell抬驴,完全由 layout 對(duì)象控制炼七。
在 layout 中添加 Decoration view 的步驟:
- 在 layout 對(duì)象中注冊(cè)自定義的 decoration view,
registerClass:forDecorationViewOfKind:
或registerNib:forDecorationViewOfKind:
方法 - layout 對(duì)象中
layoutAttributesForElementsInRect:
方法中為 decoration view 創(chuàng)建屬性 - 實(shí)現(xiàn)
layoutAttributesForDecorationViewOfKind:atIndexPath:
方法并在請(qǐng)求時(shí)返回布局屬性 - 選擇性的實(shí)現(xiàn)
initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
和finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:
來(lái)處理出現(xiàn)和消失的動(dòng)畫效果
decoration view 與 cell 和 supplementary view 的創(chuàng)建過程不同布持,僅需要注冊(cè) class 或者 nib 即可豌拙,最多調(diào)用一個(gè) initWithFrame:
方法,不需要額外的配置题暖,注意可以使用 zIndex
屬性來(lái)設(shè)置層級(jí)結(jié)構(gòu)按傅、任何 decoration view 需要是 UICollectionReusableView
子類,啟動(dòng)了回收機(jī)制胧卤。
插入和刪除動(dòng)畫
插入新的 cell 的時(shí)候唯绍,collection view 會(huì)詢問 layout 對(duì)象提供一組初始化屬性用于動(dòng)畫,結(jié)束屬性就是默認(rèn)的位置灌侣、屬性等推捐。類似的,當(dāng)刪除一個(gè) cell 的時(shí)候侧啼,collection view 會(huì)詢問 layout 對(duì)象提供一組終值屬性用于動(dòng)畫牛柒,初始屬性默認(rèn)的 indexPath 位置等。
當(dāng)插入 item 的時(shí)候痊乾,layout 對(duì)象需要提供正在要被插入的 item 的初始化 layout 信息皮壁。在此例中, layout 先將 cell 的初始位置位置到 collection view 的中間哪审,并將 alpha 設(shè)為0蛾魄,動(dòng)畫期間,此 cell 會(huì)漸漸出現(xiàn)并移動(dòng)到右下角湿滓。參考代碼:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
attributes.alpha = 0.0;
CGSize size = [self collectionView].frame.size;
attributes.center = CGPointMake(size.width / 2.0, size.height / 2.0);
return attributes;
}
需要注意的是滴须,上述代碼在插入 cell 的時(shí)候所有的 cell 都會(huì)添加此插入的動(dòng)畫,若只想對(duì)插入的 item 做插入動(dòng)畫叽奥,可以檢查 indexPath 是否與傳入的prepareForCollectionViewUpdates:
方法的 item 的 indexPath 匹配扔水,并且只有在匹配的時(shí)候才進(jìn)行動(dòng)畫,否則只返回 super 的initialLayoutAttributesForAppearingItemAtIndexPath:.
方法朝氓。
override func prepareForCollectionViewUpdates(updateItems: [UICollectionViewUpdateItem]) {
super.prepareForCollectionViewUpdates(updateItems)
insertIndexPath = [NSIndexPath]()
deleteIndexPath = [NSIndexPath]()
for update in updateItems {
switch update.updateAction {
case .Insert:
insertIndexPath.append(update.indexPathAfterUpdate!)
case .Delete:
deleteIndexPath.append(update.indexPathBeforeUpdate!)
default:
print("error")
}
if update.updateAction == UICollectionUpdateAction.Insert {
}
}
}
delete 動(dòng)畫與插入類似魔市,需要提供正確的 final layout 屬性主届。
提升 layout 的滾動(dòng)體驗(yàn)
當(dāng)滾動(dòng)相關(guān)的 touch 事件結(jié)束后,scrollview 會(huì)根據(jù)當(dāng)前的 speed 和減速狀況決定最終會(huì)停在哪個(gè)偏移待德。一旦 collection view 知道這個(gè)位置后君丁,它就會(huì)詢問 layout 對(duì)象是否修改這個(gè)位置,通過調(diào)用 targetContentOffset(forProposedContentOffset:withScrollingVelocity:)
将宪。由于是在滾動(dòng)過程中調(diào)用此方法绘闷,所以自定義 layout 可以改變滾動(dòng)的停止位置。
下圖展示了調(diào)整滾動(dòng)特性的效果涧偷。
假如 collection view 開始于(0,0)簸喂,且用戶向左滑動(dòng),collection view 計(jì)算出滾動(dòng)原本會(huì)停在如下的位置燎潮,這個(gè)值是 “proposed” content 的 offset 值喻鳄。自定義 layout 可以改變這個(gè)值,以確保滾動(dòng)停下的時(shí)候确封,某個(gè) item 正好停留在可見區(qū)域的正中間除呵。這個(gè)新值會(huì)成為新的目標(biāo)的 content offset,這個(gè)值從 targetContentOffsetForProposedContentOffset:withScrollingVelocity:
方法中返回爪喘。
例子:
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
//計(jì)算最終顯示的矩形框
var rect: CGRect = CGRectZero
rect.origin.y = 0
rect.origin.x = proposedContentOffset.x
rect.size = (collectionView!.frame.size)
//根據(jù)最終的矩形來(lái)獲得super已經(jīng)計(jì)算好的屬性
let originArray = super.layoutAttributesForElementsInRect(rect)
let attributes = NSArray(array: originArray!, copyItems: true) as? [UICollectionViewLayoutAttributes]
//計(jì)算collectionView最中心點(diǎn)的x值
let centerX = proposedContentOffset.x + collectionView!.frame.size.width * 0.5
//存放做小間距
var minDelta: CGFloat = CGFloat(MAXFLOAT)
for attrs in attributes! {
if abs(minDelta) > abs(attrs.center.x - centerX) {
minDelta = attrs.center.x - centerX
}
}
//修改原有的偏移量
return CGPointMake(proposedContentOffset.x + minDelta, proposedContentOffset.y)
}
改進(jìn)自定義布局的建議
- items 數(shù)量較少時(shí)颜曾,數(shù)百個(gè)或者 items layout 信息變化較小時(shí),可以在
prepareLayout
中創(chuàng)建并緩存UICollectionViewLayoutAttributes
布局屬性對(duì)象信息秉剑;當(dāng)items 數(shù)量達(dá)到上千個(gè)時(shí)候泛豪,需要衡量緩存和重新計(jì)算兩種方式的性能差異 - 禁止繼承
UICollectionView
- 不要在
layoutAttributesForElementsInRect:
方法中調(diào)用UCollectionView
的visibleCells
方法,因?yàn)槠鋵?shí)這個(gè)調(diào)用是轉(zhuǎn)化成了向layout對(duì)象請(qǐng)求visible cells
方法侦鹏。
Demo
Github地址:CollectionView Demo
學(xué)習(xí)方法
如何查詢 Apple 官方文檔诡曙?