CollectionView詳解

最近將 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é)作而成

UICollectionView

在上面可以看出鳖目,一個(gè) CollectionView 視圖從 data source 中獲取數(shù)據(jù)信息扮叨,data source 和 delegate 來(lái)管理具體的單元對(duì)象,包括選中领迈、未選中彻磁、高亮、未高亮等狀態(tài)狸捅。Layout 決定了每個(gè) cell 之間的邊界及布局信息衷蜓,并通過 layoutattributes 來(lái)決定每個(gè) cell 的屬性。

cv_objects

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

object

一個(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è)

supplementary views 除了重用標(biāo)識(shí)外金闽,還有一個(gè)額外的標(biāo)識(shí),每個(gè) layout 對(duì)象負(fù)責(zé)定義 supplementary view 支持的 kind 剿骨。比如 UICollectionViewFlowLayout類支持兩種類型 UICollectionElementKindSectionHeaderUICollectionElementKindSectionFooter

注意:如果使用的自定義的 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ū)別

cell_selection_semantics

顯示編輯按鈕

當(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è)置 minimumLineSpacingminimumInteritemSpacing 或者使用代理方法來(lái)實(shí)現(xiàn) collectionView:layout:minimumLineSpacingForSectionAtIndex:collectionView:layout:minimumInteritemSpacingForSectionAtIndex:

使用內(nèi)邊距設(shè)置邊緣

flow_section_insets

使用 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)用沃但。

cv_layout_process

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)畫效果。

cv_visible_elements

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ì)象

例子 自定義 collection view 布局

- (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 位置等。

custom_insert_animations

當(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)特性的效果涧偷。

custom_target_scroll_offset

假如 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)用 UCollectionViewvisibleCells方法,因?yàn)槠鋵?shí)這個(gè)調(diào)用是轉(zhuǎn)化成了向layout對(duì)象請(qǐng)求visible cells 方法侦鹏。

Demo

CollectionView Demo

Github地址:CollectionView Demo

學(xué)習(xí)方法

如何查詢 Apple 官方文檔诡曙?

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市略水,隨后出現(xiàn)的幾起案子价卤,更是在濱河造成了極大的恐慌,老刑警劉巖渊涝,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慎璧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跨释,警方通過查閱死者的電腦和手機(jī)胸私,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鳖谈,“玉大人岁疼,你說我怎么就攤上這事◎悄罚” “怎么了五续?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)龄恋。 經(jīng)常有香客問我疙驾,道長(zhǎng),這世上最難降的妖魔是什么郭毕? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任它碎,我火速辦了婚禮,結(jié)果婚禮上显押,老公的妹妹穿的比我還像新娘扳肛。我一直安慰自己,他們只是感情好乘碑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布挖息。 她就那樣靜靜地躺著,像睡著了一般兽肤。 火紅的嫁衣襯著肌膚如雪套腹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天资铡,我揣著相機(jī)與錄音电禀,去河邊找鬼。 笑死笤休,一個(gè)胖子當(dāng)著我的面吹牛尖飞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播店雅,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼政基,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了底洗?” 一聲冷哼從身側(cè)響起腋么,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亥揖,沒想到半個(gè)月后珊擂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡费变,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年摧扇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挚歧。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扛稽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滑负,到底是詐尸還是另有隱情在张,我是刑警寧澤用含,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站帮匾,受9級(jí)特大地震影響啄骇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘟斜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一缸夹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧螺句,春花似錦虽惭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至佣蓉,卻和暖如春披摄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背勇凭。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工疚膊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虾标。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓寓盗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親璧函。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傀蚌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容