最近將 UICollectionView 進行了一個全面的學習及總結,參考了網上大量的文章,把官方文檔進行了大概翻譯,最后有個小Demo。
UICollectionView基礎知識
collection view 是運用一個靈活多變的布局呈現一系列有序數據項的一種方法。collection view最通常的使用使用像網格狀排列來呈現數據項,但是 iOS 的 collection view 的能力不僅限于行和列。使用 collection views, 視覺元素的精確布局可通過子類化定義并被動態改變。所以你可以實現網格,棧,圓形布局,動態改變布局,或任何你可以想象的排列布局。
Collection View 是由多個對象協作而成
在上面可以看出,一個 CollectionView 視圖從 data source 中獲取數據信息,data source 和 delegate 來管理具體的單元對象,包括選中、未選中、高亮、未高亮等狀態。Layout 決定了每個 cell 之間的邊界及布局信息,并通過 layoutattributes 來決定每個 cell 的屬性。
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
一個有效的數據源使用節和元素來組織基礎的數據對象。一個簡單的解決方案就是如圖一樣,把數據源組織成一個嵌套數組,最外層數據包含一個或多個數據展示數據源中的節,每個節數據包含多個元素。當設計數據結構時,要考慮性能問題,集合視圖訪問數據源僅是為了計算一共多少元素并獲取當前屏幕所需的數據對象。如果布局對象只是依賴于數據對象,當數據對象包含上千條數據的時候,性能會受到大幅的影響。
告訴集合視圖相關的數據源信息
當以下發生時,需要提供給集合視圖相關 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:
方法來注冊
- 注冊 cell,采用
supplementary views 除了重用標識外,還有一個額外的標識,每個 layout 對象負責定義 supplementary view 支持的 kind 。比如 UICollectionViewFlowLayout
類支持兩種類型 UICollectionElementKindSectionHeader
和 UICollectionElementKindSectionFooter
注意:如果使用的自定義的 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 中執行一個長按的手勢,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 之間的間隔
注意此處設置的是最小間隔,實際間隔可能大小此間隔。可以通過屬性設置 minimumLineSpacing
和 minimumInteritemSpacing
或者使用代理方法來實現 collectionView:layout:minimumLineSpacingForSectionAtIndex:
和 collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
使用內邊距設置邊緣
使用 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 刷新的時候才會調用。
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 的信息,因為有可能在插入及刪除時做動畫效果。
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 返回屬性對象
- (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 位置等。
當插入 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 可以改變滾動的停止位置。
下圖展示了調整滾動特性的效果。
假如 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:
方法中調用UCollectionView
的visibleCells
方法,因為其實這個調用是轉化成了向layout對象請求visible cells
方法。
Demo
Github地址:CollectionView Demo
學習方法
如何查詢 Apple 官方文檔?