CollectionView詳解

最近將 UICollectionView 進行了一個全面的學習及總結,參考了網上大量的文章,把官方文檔進行了大概翻譯,最后有個小Demo。

UICollectionView基礎知識

collection view 是運用一個靈活多變的布局呈現一系列有序數據項的一種方法。collection view最通常的使用使用像網格狀排列來呈現數據項,但是 iOS 的 collection view 的能力不僅限于行和列。使用 collection views, 視覺元素的精確布局可通過子類化定義并被動態改變。所以你可以實現網格,棧,圓形布局,動態改變布局,或任何你可以想象的排列布局。

Collection View 是由多個對象協作而成

UICollectionView

在上面可以看出,一個 CollectionView 視圖從 data source 中獲取數據信息,data source 和 delegate 來管理具體的單元對象,包括選中、未選中、高亮、未高亮等狀態。Layout 決定了每個 cell 之間的邊界及布局信息,并通過 layoutattributes 來決定每個 cell 的屬性。

cv_objects

ReusableView 采用的可重用 cell 的方法,可以提高效率,支持三種不同的可重用的view

  • cell 展示了 collection view 的主要內容,cell 的內容來自你的數據對象,每個 cell 必須是 UICollectionViewCell 類的實例。cell 對象可以管理自己的選中和高亮狀態
  • Supplementary views 展示了關于 section 的信息,同樣也是數據驅動的,是可選的。
  • Decoration views 裝飾視圖,不與數據相關,完全屬于 layout 對象。

Layout 對象控制視覺顯示

layout object 決定著 cell 的大小,位置還有其他顯示相關的屬性。
layout object 沒有接觸任何視圖,它只是一個屬性集合。而沒有任何限制,你可以隨你所想。
layout object 不止可以控制視圖的寬高尺寸,還可以控制視圖之間的關系屬性,透明度,3D,和他跟其他視圖之間的可見性對比。

Collection Views Initiate Animations Automatically

collection views 在 cell 移動、插入或刪除的時候,會有默認的動畫??梢宰远x這些動畫。

設計 Data Source 和 Delegate

每個 collection view 必須有一個 data source 的對象,是 app 展示的內容。必須提供 collection view 需要的信息,比如多少個 items 等。delegate 對象一般用于和內容的交互相關的??梢怨芾?cell 的高亮選中狀態,除此之外還有額外的工作,比如自定義布局的 cell 的大小及間隔 spacing 等。

用 Data Source 管理你的內容

data source 需要遵循 UICollectionViewDataSource 協議提供三個信息:

  • collection view 包括多少個 sections
  • 每個 section 包括多少個 items
  • 每個 cell 展示的內容

幾個方法

  • 展示數量
    • collectionView(_:numberOfItemsInSection:)
    • numberOfSectionsInCollectionView(_:)
  • 展示 cell
    • collectionView(_:cellForItemAtIndexPath:)
    • collectionView(_:viewForSupplementaryElementOfKind:atIndexPath:)
  • cell 重排
    • collectionView(_:canMoveItemAtIndexPath:)
    • collectionView(_:moveItemAtIndexPath:toIndexPath:)

設計你的 Data Objects

object

一個有效的數據源使用節和元素來組織基礎的數據對象。一個簡單的解決方案就是如圖一樣,把數據源組織成一個嵌套數組,最外層數據包含一個或多個數據展示數據源中的節,每個節數據包含多個元素。當設計數據結構時,要考慮性能問題,集合視圖訪問數據源僅是為了計算一共多少元素并獲取當前屏幕所需的數據對象。如果布局對象只是依賴于數據對象,當數據對象包含上千條數據的時候,性能會受到大幅的影響。

告訴集合視圖相關的數據源信息

當以下發生時,需要提供給集合視圖相關 data source

  • collection view 第一次顯示的時候
  • 給 collection view 指定了不同的 data source
  • 明確調用了 reloadData 方法
  • collection view 的代理執行了 performBatchUpdates:completion: 方法,或者任何移動、插入、刪除方法執行的時候
collectionView.performBatchUpdates({ () -> Void in
            collectionView.insertItemsAtIndexPaths(insertIndexPaths)
            collectionView.moveItemAtIndexPath(currentIndexPath, toIndexPath: toIndexPath)
            }, completion: { (isFinish) -> Void in
        })

通過numberOfSectionsInCollectionView:來提供分組數量,可選的方法,默認是1,使用collectionView:numberOfItemsInSection:來提供每個分組的 items 的數量。

配置單元格(cells)和增補視圖(supplementa views)

要給單元提供內容,需要做的兩件事:

  • 在 storyboard 文件中嵌入模板單元,(可以注冊一個 class 或者 nib 文件)
  • 在數據源中,重用出列(dequeue)并且配置相應的 cell 單元

復用標識(Reuse identifiers)可以讓注冊多種類型的單元和增補視圖成為可能。當請求一個視圖或一個對象時,可以使用提供的索引路徑決定想要使用哪種類型的視圖和單元格,然后傳遞適當的重用標識給 dequeue 函數。

注冊單元格和增補視圖

兩種方式注冊單元格,在 storyboard 中注冊或者手動代碼注冊。

  • 使用 storyboard 文件,拖拽 cell 或者 supplementary views 到 storyboard 中,并且配置相關屬性,設置重用標識。
  • 使用手動代碼的方式注冊
    • 注冊 cell,采用registerClass:forCellWithReuseIdentifier:或者 registerNib:forCellWithReuseIdentifier: 方法來注冊 cell
    • 注冊 supplementary views,采用registerClass:forSupplementaryViewOfKind:withReuseIdentifier: 或者 registerNib:forSupplementaryViewOfKind:withReuseIdentifier: 方法來注冊

supplementary views 除了重用標識外,還有一個額外的標識,每個 layout 對象負責定義 supplementary view 支持的 kind 。比如 UICollectionViewFlowLayout類支持兩種類型 UICollectionElementKindSectionHeaderUICollectionElementKindSectionFooter

注意:如果使用的自定義的 layouts,需要自己定義 supplementary views 的類型。

重用和配置單元格和增補視圖

data source 對象負責提供 cell 和 supplementary view 的內容,包含兩個方法: collectionView:cellForItemAtIndexPath:collectionView:viewForSupplementaryElementOfKind:atIndexPath:,實現兩個方法的簡單步驟:

  • 重用 cell 或 supplementary views
    • dequeueReusableCellWithReuseIdentifier:forIndexPath:
    • dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
  • 對于指定的IndexPath利用數據來配置 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
    }

插入、刪除、移動 sections 和 items

插入、刪除,移動單個 section 或者 items,必須遵循兩個步驟:

  • 在 datasource 對象中更新數據
  • 調用相應的插入、刪除等方法

當插入、刪除和移動單個 item 的時候,collection view 的方法會對這些變化自動產生一個動畫效果。如果想多個改變共用一個動畫,必須在performBatchUpdates:completion:方法的閉包里執行插入、刪除、移動等。

例子:

 collectionView.performBatchUpdates({ 
            let itemPaths = collectionView.indexPathsForSelectedItems()
            //從 data source 中刪除數據
            deleteItemFromDataSourceAtIndexPaths()
            collectionView.deleteItemsAtIndexPaths(itemPaths)
            }, completion: nil)

管理 cell 的選中、高亮狀態

collection view 支持單個 item 選中,也可以配置為多個 item 選中或者禁止選中。當 selectedBackgroundView 中包含一個有效的 view 的時候,當 cell 是高亮或選中狀態時會顯示這個 view。此種方式可以改變 cell 高亮或選中時的背景顏色。

collection views 的代理提供的一些方法:

  • collectionView:shouldSelectItemAtIndexPath:
  • collectionView:shouldDeselectItemAtIndexPath:
  • collectionView:didSelectItemAtIndexPath:
  • collectionView:didDeselectItemAtIndexPath:
  • collectionView:shouldHighlightItemAtIndexPath:
  • collectionView:didHighlightItemAtIndexPath:
  • collectionView:didUnhighlightItemAtIndexPath:

改變 cell 背景顏色的一個例子

- (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;
}

高亮和選中區別

cell_selection_semantics

顯示編輯按鈕

當在 cell 中執行一個長按的手勢,collection view 可以顯示一個編輯按鈕,可以用于剪切、粘貼、復制這個 cell。編輯按鈕能被顯示必須滿足以下條件:

  • 代理必須實現三個方法
    • collectionView:shouldShowMenuForItemAtIndexPath: 是否顯示菜單
    • collectionView:canPerformAction:forItemAtIndexPath:withSender: 菜單中哪些編輯操作可以顯示
    • collectionView:performAction:forItemAtIndexPath:withSender: 對于顯示的編輯操作怎么執行
  • 支持三種編輯操作 cut: copy: paste:

layouts 布局之間的轉場

最簡單的在兩個布局之間轉變的方法是 setCollectionViewLayout:animated:。如果需要控制轉場動畫或者制作可交互的轉場動畫,需要使用 UICollectionViewTransitionLayout 對象。

使用 UICollectionViewTransitionLayout 對象的步驟:

  • 使用 initWithCurrentLayout:nextLayout: 方法創建類
  • 周期性的修改 transitionProgress 屬性的值,在改變過渡的進度后,使用 collection view 的 `` 方法使布局無效。
  • collectionView:transitionLayoutForOldLayout:newLayout: 代理方法,返回過渡的布局對象
  • 選擇性的使用 updateValue:forAnimatedKey: 方法修改布局對象的值,穩定值為0

使用流水布局 Flow Layout

配置 flow layout 的幾個步驟:

  • 生成一個 flow layout對象并且指定給相應的 collection view
  • 配置 cells 的寬度和高度
  • 設置行間隔和單元間隔
  • 設置 headers 和 footers 的大小
  • 設置滾動方向

自定義屬性

指定 item 的大小

對于所有的 items 可以使用屬性 itemSize 來設置大小。如果設定不同的 itemSize,需要實現 collectionView:layout:sizeForItemAtIndexPath: 方法

指定 items 和 lines 之間的間隔

注意此處設置的是最小間隔,實際間隔可能大小此間隔。可以通過屬性設置 minimumLineSpacingminimumInteritemSpacing 或者使用代理方法來實現 collectionView:layout:minimumLineSpacingForSectionAtIndex:collectionView:layout:minimumInteritemSpacingForSectionAtIndex:

使用內邊距設置邊緣

flow_section_insets

使用 Flow Layout 的子類

可以自己生成一個 flowlayout 的子類,然后更改其中的屬性方法等實現自定義。

生成自定義的布局 Layout

實現自定義的布局不難,最難的如何在布局中計算 items 位置。

實現 UICollectionViewLayout 子類

繼承 UICollectionViewLayout 需要完成的重要的內容:

  • 指定可滾動內容區域的大小 size
  • 提供每個 cell 或 view 的layout屬性,以便 collection view 可以展示這些 cells

理解 Layout 的布局過程

invalidateLayout 會使當前的 layout 無效,并觸發 layout 的更新,會強迫 layout 對象重新計算 layout 屬性。與 reloadData 不一樣,當 data source 中的數據發生改變,適合用 reloadData 方法。

在布局過程中,會按順序調用一下函數,可以在這些方法中計算 item 的位置信息。

  • prepareLayout 方法來執行一些準備工作,可以進行一些 layout 布局需要的計算等
  • collectionViewContentSize 方法用來返回整個內容的 content size 大小
  • layoutAttributesForElementsInRect: 方法用來返回在指定的矩形區域內的 cells 的屬性等信息

prepareLayout是專門用來準備布局的,在prepareLayout方法里面我們可以事先就計算后面要用到的布局信息并存儲起來,防止后面方法多次計算,提高性能。例如,我們可以在此方法就計算好每個 cell 的屬性、整個 CollectionView 的內容尺寸等等。此方法在布局之前會調用一次,之后只有在調用invalidateLayout、shouldInvalidateLayoutForBoundsChange:返回YES和 UICollectionView 刷新的時候才會調用。

cv_layout_process

prepareLayout方法是為確定布局中各 cell 和 view 位置做計算,需要在此方法中算出足夠的信息以供后續方法計算內容區域的整體 size,collection view 使用 content size 以正確地配置 scroll view。比如 content size 長寬均超過屏幕的話,水平與豎直方向的滾動都會被 enable。基于當前滾動位置,collection view 會調用 layoutAttributesForElementsInRect:方法以請求特定 rect (有可能是也可能不是可見 rect)中 cell 和 view 的屬性。到此,core layout process 已經結束了。

layout 結束之后,cells 和views 的屬性在你或者 collection view invalidate布局之前都不會變。調用 invalidateLayout 方法會導致新的一次 layout process 開始,以調用 prepareLayout 方法開始。collection view 可以在滾動的過程中自動 invalidate 布局,用戶滾動內容過程中,collection view調用layout 的 shouldInvalidateLayoutForBoundsChange: 方法,如果返回值為 YES 則 invalidate 布局。(但需要知道的是,invalidateLayout 并不會馬上觸發 layout update process,而是在下一個view更新周期中,collection view 發現 layout 已經 dirty 才會去更新)
。

創建布局屬性 Layout Attributes

自定義 layout 需要返回一個 UICollectionViewLayoutAttributes 類的對象,這些對象可以在很多不同的方法中創建,但創建時間可以根據具體情況決定。如果 collection view 不會處理上千個 items 時,則 prepareLayout 創建會比用戶滾動過程中用到時在計算更高效,因為創建的屬性可以緩存起來。如果計算所有屬性并緩存起來所帶來的性能消耗比請求時在計算屬性的消耗更大,則可以在請求的時候在計算相關屬性。

UICollectionViewLayoutAttributes的屬性:

  • frame
  • bounds
  • center
  • size
  • transform3D
  • transform
  • alpha
  • zIndex
  • hidden

創建 UICollectionViewLayoutAttributes 類對象時,可以使用一些方法:

  • init(forCellWithIndexPath indexPath: NSIndexPath)
  • init(forSupplementaryViewOfKind:withIndexPath:)
  • init(forDecorationViewOfKind:withIndexPath:)

view 的類型不同,必須使用正確的類方法,因為 collection view 會根據這些信息向 data source 對象請求適當類型的 view,使用錯誤的方法在錯誤的地方創建錯誤的 view。

創建每個屬性對象后,要將相應的 view 的相關屬性設置上。最基本的要設置 view 的 size 和 position 信息。如果布局中有 view 重疊了,需要配置正確的 zIndex 屬性來維持有序的狀態。其他屬性可以控制 cell 或 view 的外觀及可見性。

準備 Layout

在一個布局周期中,首先會調用 prepareLayout 方法,可以來執行一些準備工作,可以進行一些 layout 布局需要的計算等,可以存儲一些 layout attributes 信息。

給定矩形中的 items 布局屬性

layout process 的最后,collection view 會調用 layoutAttributesForElementsInRect: 方法,對于一個大的可滾動內容區域,collection view 可能只會請求當前可見的那部分區域中的所有 items 屬性。這個方法支持獲取任意 rect 中 items 的信息,因為有可能在插入及刪除時做動畫效果。

cv_visible_elements

layoutAttributesForElementsInRect: 方法實現需要尊重如下的步驟:

  • 遍歷 prepareLayout 方法產生的數據以訪問緩存的屬性或創建新的屬性
  • 檢查每個 item 中的 frame 以確定是否與 layoutAttributesForElementsInRect: 方法中指定的 rect 有重疊部分
  • 對每個重疊的 item ,添加一個對應的 UICollectionViewLayoutAttributes 對象到一個數組中
  • 返回布局屬性的數組給 collection view

不僅要記住緩存layout信息能夠帶來性能提升,也要記住不斷重復為cells創建新layout屬性的計算代價是十分昂貴的,足以影響到app的性能。當collection view管理的items量很大時,采用在請求時創建layout屬性的方式是十分合理的。

按需提供布局屬性

collection view 會在正常的 layout 過程之外周期性的讓你提供單個 items 的layout 對象。比如為某 item 配置插入和刪除對話。通過以下方法提供信息:

  • layoutAttributesForItemAtIndexPath:
  • layoutAttributesForSupplementaryViewOfKind:atIndexPath:
  • layoutAttributesForDecorationViewOfKind:atIndexPath:

layoutAttributesForItemAtIndexPath: 所有自定義的 layout 必須重寫的方法。
當返回屬性時,不應該更新這些 layout 屬性,如果需要改變 layout 信息,調用 invalidateLayout 在接下來的 layout 周期中更新這些信息。

兩種方式設置 collection view 的 layout 為自定義的 layout,

  • 一種方式在 storyboard 文件中,在 Attributes inspector 中設置 Layout 從 Flow 改為 Custom。
  • 另外一種直接代碼設置 self.collectionView.collectionViewLayout = [[MyCustomLayout alloc] init];

讓你的 Layout 更優異

除了上述必須實現的方法,還有一些特性能夠改善自定義的 layout 的用戶體驗,實現這些屬性是可選但是推薦實現的。

通過附加 view 提供內容品質

supplementary views 與 cells 分離并且有自己的 layout 屬性,由 data source 提供,其目的是為 app 主要內容增強信息。UICollectionViewFlowLayout 中使用 supplementary view 來作為 section headers 和 footers,除此之外,可以使用 supplementary views 給每個 cell 提供一個自己的 label,用于顯示 cell 的信息。與 cells 一樣,supplementary views 也需要重用,所有 supplementary views 需要繼承自 UICollectionReusableView 類。

添加 supplementary views 到 layout 的過程如下:

  • 注冊 supplementary views 到 layout 對象中,registerClass:forSupplementaryViewOfKind:withReuseIdentifier: 或者 registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
  • 在 data source 中實現 collectionView:viewForSupplementaryElementOfKind:atIndexPath: 方法,由于這些 view 是可重用的,調用 dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:來獲得 view
  • 像為 cells 創建屬性一樣,為 supplementary views 創建 layout 屬性
  • layoutAttributesForElementsInRect: 方法中返回的屬性數組中包含 supplementary views 的 layout 屬性
  • 如果需要的話,實現 layoutAttributesForSupplementaryViewOfKind:atIndexPath: 方法為特定的 supplementary views 返回屬性對象

例子 自定義 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。這是因為 supplementary view 與它們是分離開的,是為了烘托主旨,所以每個 supplementary view 方法都會指明其類別 kind 以方便正確計算其特有的屬性。

在 layout 中添加 Decoration Views

Decoration views 是 Layout UI 特征的有效點綴,它僅僅提供一些可視化的內容,與 data source 無關??梢杂脕碜远x背景,在 cells 縫隙之間填充,甚至可以掩蓋 cell,完全由 layout 對象控制。

在 layout 中添加 Decoration view 的步驟:

  • 在 layout 對象中注冊自定義的 decoration view,registerClass:forDecorationViewOfKind:registerNib:forDecorationViewOfKind: 方法
  • layout 對象中 layoutAttributesForElementsInRect: 方法中為 decoration view 創建屬性
  • 實現 layoutAttributesForDecorationViewOfKind:atIndexPath:方法并在請求時返回布局屬性
  • 選擇性的實現 initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath: 來處理出現和消失的動畫效果

decoration view 與 cell 和 supplementary view 的創建過程不同,僅需要注冊 class 或者 nib 即可,最多調用一個 initWithFrame: 方法,不需要額外的配置,注意可以使用 zIndex屬性來設置層級結構、任何 decoration view 需要是 UICollectionReusableView 子類,啟動了回收機制。

插入和刪除動畫

插入新的 cell 的時候,collection view 會詢問 layout 對象提供一組初始化屬性用于動畫,結束屬性就是默認的位置、屬性等。類似的,當刪除一個 cell 的時候,collection view 會詢問 layout 對象提供一組終值屬性用于動畫,初始屬性默認的 indexPath 位置等。

custom_insert_animations

當插入 item 的時候,layout 對象需要提供正在要被插入的 item 的初始化 layout 信息。在此例中, layout 先將 cell 的初始位置位置到 collection view 的中間,并將 alpha 設為0,動畫期間,此 cell 會漸漸出現并移動到右下角。參考代碼:

- (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 的時候所有的 cell 都會添加此插入的動畫,若只想對插入的 item 做插入動畫,可以檢查 indexPath 是否與傳入的prepareForCollectionViewUpdates: 方法的 item 的 indexPath 匹配,并且只有在匹配的時候才進行動畫,否則只返回 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 動畫與插入類似,需要提供正確的 final layout 屬性。

提升 layout 的滾動體驗

當滾動相關的 touch 事件結束后,scrollview 會根據當前的 speed 和減速狀況決定最終會停在哪個偏移。一旦 collection view 知道這個位置后,它就會詢問 layout 對象是否修改這個位置,通過調用 targetContentOffset(forProposedContentOffset:withScrollingVelocity:) 。由于是在滾動過程中調用此方法,所以自定義 layout 可以改變滾動的停止位置。

下圖展示了調整滾動特性的效果。

custom_target_scroll_offset

假如 collection view 開始于(0,0),且用戶向左滑動,collection view 計算出滾動原本會停在如下的位置,這個值是 “proposed” content 的 offset 值。自定義 layout 可以改變這個值,以確保滾動停下的時候,某個 item 正好停留在可見區域的正中間。這個新值會成為新的目標的 content offset,這個值從 targetContentOffsetForProposedContentOffset:withScrollingVelocity: 方法中返回。

例子:

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {        
        //計算最終顯示的矩形框
        var rect: CGRect = CGRectZero
        rect.origin.y = 0
        rect.origin.x = proposedContentOffset.x
        rect.size = (collectionView!.frame.size)        
        //根據最終的矩形來獲得super已經計算好的屬性
        let originArray = super.layoutAttributesForElementsInRect(rect)
        let attributes = NSArray(array: originArray!, copyItems: true) as? [UICollectionViewLayoutAttributes]
        //計算collectionView最中心點的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)
        
    }

改進自定義布局的建議

  • items 數量較少時,數百個或者 items layout 信息變化較小時,可以在 prepareLayout 中創建并緩存 UICollectionViewLayoutAttributes 布局屬性對象信息;當items 數量達到上千個時候,需要衡量緩存和重新計算兩種方式的性能差異
  • 禁止繼承 UICollectionView
  • 不要在 layoutAttributesForElementsInRect:方法中調用 UCollectionViewvisibleCells方法,因為其實這個調用是轉化成了向layout對象請求visible cells 方法。

Demo

CollectionView Demo

Github地址:CollectionView Demo

學習方法

如何查詢 Apple 官方文檔?

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容