iOS UICollectionView 全解
什么是UICollectionView?
UICollectionView是一種新的數(shù)據(jù)展示方式,簡單來說可以把他理解成多列的UITableView。它有許多與UITableView相同的地方,例如:數(shù)據(jù)源、代理等接口等。既然,UITableView有這么多的相似之處,為什么還要學(xué)習(xí)CollectionView呢?作為一個(gè)獨(dú)立的控件,CollectionView有著自己獨(dú)特的布局特性,這一點(diǎn)拉開了兩個(gè)控件的差距,所以學(xué)習(xí)UIcollectionView還是非常有必要的。
UICollectionView 組成
![]()
如圖:你看到的就是一個(gè)最簡單的UICollectionView,它包含:Cells、Supplementary Views、Decoration Views。
1.Cells :用于展示內(nèi)容的主體,cell的尺寸和內(nèi)容可以各不相同。這個(gè)下面會(huì)詳細(xì)介紹。
2.Supplementary Views : 追加視圖,類似于UITableView每個(gè)Seciton的Header View 或者Footer View,用來標(biāo)記Section的View
3.Decoration Views :裝飾視圖,完全跟數(shù)據(jù)沒有關(guān)系的視圖,負(fù)責(zé)給cell 或者supplementary Views添加輔助視圖用的,靈活性較強(qiáng)。
不管多么復(fù)雜的UIcollectionView都是由著三個(gè)部件組成的。
實(shí)現(xiàn)一個(gè)簡單的UICollectionView
相信如果你對UITableView有所了解的話,實(shí)現(xiàn)一個(gè)UICollectionView,其實(shí)和UITableView沒有多大的區(qū)別,它們同樣是由dataSource和delegate模式設(shè)計(jì)的:dataSource為View提供數(shù)據(jù)源,告訴View要顯示什么樣的數(shù)據(jù),delegate提供一些樣式的小細(xì)節(jié)以及用戶交互的響應(yīng)。但是值得一提的是要想初始化一個(gè)UICollectionView,一定要為它提供一個(gè)布局,即:UICollectionViewLayout對象,改對象定義了CollectionView的一些獨(dú)特的布局,后面會(huì)詳細(xì)的介紹。
UICollectionViewDataSource
- section數(shù)量 :
numberOfSectionsInCollectionView:
某個(gè)section中多少個(gè)item:collectionView:numberOfItemsInSection: - 對于某個(gè)位置的顯示什么樣的cell:
collectionView:cellForItemAtIndexPath: - 對于某個(gè)section顯示什么樣的supplementary View:
collectionView:viewForSupplementaryElementOfKind:atIndexPath:
對于Decoration Views,提供的方法并不在UICollectionViewDataSource中,而是在UICollectionViewLayout中(因?yàn)樗鼉H僅跟視圖相關(guān),而與數(shù)據(jù)無關(guān))。
UICollectionView子視圖重用
與UITableView一樣,避免不斷的生成和銷毀對象,UICollectionView對于view的重用是必須的。但是我們發(fā)現(xiàn)在UIcollectionview中不僅僅是cell的重用,Supplementary View和Decoration View也是可以并且應(yīng)當(dāng)被重用的,細(xì)心的朋友可以發(fā)現(xiàn),UIcollectionViewCell繼承自UICollectionReusableView,而在collectionView:viewForSupplementaryElementOfKind:atIndexPath:這個(gè)方法的返回類型是:UICollectionReusableView,其實(shí)蘋果給UIcollectionview中的所有視圖都來自一個(gè)可重用的基類,就是UICollectionReusableView,所以自定義Supplementary View和Decoration View也是繼承者個(gè)類為基礎(chǔ),來構(gòu)建自己的視圖。
- 注冊自定義視圖
- registerClass:forCellWithReuseIdentifier:
- registerNib:forCellWithReuseIdentifier:
- registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
- registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
得到對應(yīng)的視圖 - dequeueReusableCellWithReuseIdentifier:forIndexPath:
- dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
其中SupplementaryViewOfKind:參數(shù)的類型是一個(gè)字符串常量,UICollectionElementKindSectionHeader/UICollectionElementKindSectionFooter該常量的定義UICollectionViewDelegateFlowLayout類中。
UICollectionViewDelegate
和數(shù)據(jù)無關(guān)的view的外形,用戶的交互什么的,都是UICollectionViewDelegate來負(fù)責(zé)
- cell的高亮
- cell的選中狀態(tài)
- 可以支持長安后的菜單
- 在用戶交互上UIcollectionview做了很多細(xì)節(jié)的處理,每個(gè)cell有獨(dú)立的高亮、選中事件的delegate,開發(fā)者可以監(jiān)聽到的事件變得多起來,當(dāng)用戶點(diǎn)擊一個(gè)cell的時(shí)候,會(huì)按以下的流程delegate詢問(這是UIcollectionViewDelegate API中給出的):
(when the touch begins)
- 1、-collectionView:shouldHighlightItemAtIndexPath:是否應(yīng)該高亮?
- 2、-collectionView:didHighlightItemAtIndexPath:如果1返回YES,執(zhí)行,否則不執(zhí)行
(when the touch lifts) - 3、-collectionView:shouldSelectItemAtIndexPath: or -collectionView:shouldDeselectItemAtIndexPath:如果1返回NO這里不再詢問詢問是否選中,
- 4、-collectionView:didSelectItemAtIndexPath: or -collectionView:didDeselectItemAtIndexPath:如果3中返回選中,調(diào)用選中
- 5、-collectionView:didUnhighlightItemAtIndexPath:如果1返回YES,那么會(huì)調(diào)用取消高亮
在UIcollectionview中分別有selected和highlighted屬性控制狀態(tài)。
UICollectionViewCell
相對于UItableViewCell而言,UIcollectionViewCell沒有那么多樣式。UIcollectionViewCell不存在所謂的style,也沒有titleLabel和內(nèi)置的imageView屬性,UIcollectionViewCell的結(jié)構(gòu)上相對比較簡單,由下至上:
cell:本身作為的View,這里應(yīng)該就是UICollectionReusableViewbackgroundView:用作cell背景的視圖,設(shè)置背景圖片等。selectedBackgroundView:cell被選中的背景視圖contentView:內(nèi)容視圖,自定義的cell時(shí)應(yīng)該將內(nèi)容放在這個(gè)View上
UIcollectionView有一個(gè)小細(xì)節(jié):被選中的cell的自動(dòng)變化,所有的cell中的子View,也包括contentView中的子View,當(dāng)cell被選中是,會(huì)自動(dòng)去查找view是否有被選中狀態(tài)下的改變,如果在contentView中有一個(gè)imageView的selected和normal狀態(tài)下的圖片是不同的,那么選中cell這張圖片也會(huì)從normal變成selected,不需要添加代碼。
UICollectionView 布局 UICollectionViewLayout
UICollectionViewLayout是UICollectionView特有的,是UICollectionView的精髓所在,它負(fù)責(zé)將每個(gè)cell、supplementary view、decoration view進(jìn)行組合,為它們設(shè)置各自的屬性,包括:位置、大小、透明度、層級關(guān)系、形狀等。UICollectionViewLayout決定了,UICollectionView是如何顯示在界面上,從UICollectionView初始化必須要一個(gè)UICollectionViewLayout也可以看得出來,Layout對于UICollectionView的最要性。
UICollectionViewFlowLayout 流水布局
蘋果為我們設(shè)計(jì)了一套非常靈活、通用的Layout,就是UICollectionViewFlowLayout流水布局,也叫線性布局。UICollectionViewFlowLayout屬性:
CGSize itemSize:它定義了每一個(gè)item的大小,通過itemSize可以快捷給每一個(gè)cell設(shè)置一樣的大小,如果你想到不同的尺寸,-collectionView:layout:sizeForItemAtIndexPath:來給每一個(gè)item指定不同的尺寸。CGFloat minimumLineSpacing:最小行間隔,同樣你也可以通過-* collectionView:minimumLineSpacingForSectionAtIndex:方法來個(gè)沒一行設(shè)置不同的行間距CGFloat minimumInteritemSpacing:最小cell之間的距離,同上都是可以通過-collectionView:minimumInteritemSpacingForSectionAtIndex:特定的方法,頂?shù)椎骄唧w的行和item之間的間距的,非常的靈活。UIEdgeInsets sectionInset:組內(nèi)邊距,設(shè)置UIcollectionView整體的組內(nèi)邊距,同上有特定方法-collectionView:insetForSectionAtIndex:設(shè)置具體的邊距CGSize headerReferenceSize:設(shè)置supplementary header View的大小-collectionView:referenceSizeForHeaderInSection:CGSize footerReferenceSize:設(shè)置supplementary header View的大小-collectionView:referenceSizeForFooterInSection:UICollectionViewScrollDirection scrollDirection:設(shè)置UIcollectionView的滾動(dòng)方向
UICollectionViewFlowLayout方法:
// 1、準(zhǔn)備布局,布局初始化一般在這里進(jìn)行
- (void)prepareLayout;
// 每當(dāng)collectionView邊界改變時(shí)便調(diào)用這個(gè)方法詢問 是否 重新初始化布局 是則調(diào)用prepareLayout準(zhǔn)備布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
// 2、初始化布局時(shí)調(diào)用,返回在一定區(qū)域內(nèi),每個(gè)cell和Supplementary和Decoration的布局屬性
- (NSArray *)layoutAttributesForElementsInRect: (CGRect)rect;
// 當(dāng)滾動(dòng)停止時(shí),會(huì)調(diào)用該方法確定collectionView滾動(dòng)到的位置
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
在使用UICollectionViewFlowLayout布局的時(shí)候,有時(shí)會(huì)有特別的需求,比如:當(dāng)一個(gè)cell滑動(dòng)到屏幕中點(diǎn)的時(shí)候放大,并且當(dāng)我停止滑動(dòng)時(shí),能夠?qū)㈦x屏幕最近的cell居中。這四個(gè)方法就能輕松的完成這樣的事。
UICollectionViewLayoutAttributes 布局屬性
在了解這個(gè)類之前,我們得先疏通一下,UIcollectionView的布局方式,首先我們之前一直提,UIcollectionView的初始化必須有一個(gè)UICollectionViewLayout,也就是我們說的,必須要有一個(gè)布局格式樣式,那么一個(gè)UIcollectionView有那么多的cell、supplementary View、decoration View,UIcollectionViewLayout是如何進(jìn)行布局顯示的呢?原來從UIcollectionViewLayout開始加載內(nèi)容的時(shí)候,便默默的做了很多事:首先是去調(diào)用 1 準(zhǔn)備布局,然后根據(jù)當(dāng)前屏幕所處位置的合適rect,得到每一個(gè)視圖的UICollectionViewLayoutAttributes屬性,然后在把視圖按UICollectionViewLayoutAttributes中的屬性描述設(shè)置視圖具體的center、size等等,期間也會(huì)去調(diào)用其他方法去確定一些間距。所以UICollectionViewLayoutAttributes是每個(gè)視圖決定性的布局的屬性。
- CGRect frame 布局視圖的frame簡單明了
- CGPoint center 視圖中心點(diǎn)
- CGSize size 視圖尺寸
- CATransform3D transform3D 這個(gè)屬性可以用來做酷炫的3D動(dòng)畫
- CGAffineTransform transform 轉(zhuǎn)場屬性
- CGFloat alpha 透明度
- NSInteger zIndex 層級,數(shù)字越大,層級越高(最上面)。
- NSIndexPath *indexPath 如果是cell有對應(yīng)的indexPath
- UICollectionElementCategory representedElementCategory 視圖標(biāo)記,是cell還是supplementary View或者decoration View
- registerClass:forDecorationViewOfKind: 注冊decoration View
- registerNib:forDecorationViewOfKind:
- +(instancetype)layoutAttributesForDecorationViewOfKind:(NSString )decorationViewKind withIndexPath:(NSIndexPath )indexPath 這個(gè)類方法是decoration View布局的來源
- -(nullable UICollectionViewLayoutAttributes )layoutAttributesForDecorationViewOfKind:(NSString )elementKind atIndexPath:(NSIndexPath *)indexPath 與上一個(gè)方法結(jié)合得到decoration View布局
UICollectionViewLayout 自定義布局
由于系統(tǒng)給我們設(shè)計(jì)的流水局在實(shí)現(xiàn)和細(xì)節(jié)處理上,幫我們做了很多事,所以完全的自定義布局是挺麻煩,并且要考慮到各種細(xì)節(jié)的。在UIcollectionViewLayout頭文件中,蘋果給我提供很多特定的方法,以供我們自己布局,有僅僅關(guān)于布局的、有關(guān)于刪除插入item的布局、有關(guān)于移動(dòng)item的布局。方法分的很細(xì),我們只了解基本的布局、刪除和插入布局等。
- @interface UICollectionViewLayout (UISubclassingHooks) 這個(gè)擴(kuò)展類中,都是用來布局UIcollectionView子視圖的
- @interface UICollectionViewLayout (UIUpdateSupportHooks) 用來布局刪除插入動(dòng)作
- @interface UICollectionViewLayout (UIReorderingSupportHooks) 移動(dòng)動(dòng)作布局
UISubclassingHooks
這個(gè)擴(kuò)展類里面的方法,上面流水布局的代碼處已經(jīng)給列舉出來,就不多說了
UIUpdateSupportHooks
在UIcollectionView中刪除或者插入item時(shí),建議使用下面的方式:
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:@[pinchIndexPath]];
} completion:nil];
[self.collectionView performBatchUpdates:^{
[self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
} completion:nil];
值得一說的是,在對UIcollectionView插入和刪除操作是,不管使用的是上面的更新方式還是reloadData,collectionView都不會(huì)刷新全部的cell,具體原因本人也不是很清楚,多說無益,下面上刪除、插入的代碼:
/* 在insert或者delete之前,prepareForCollectionViewUpdates:會(huì)調(diào)用,
可以使用這個(gè)方法來完成 添加/ 刪除的預(yù)處理,將要?jiǎng)h除或者插入的indexPath保存
*/
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems {
// Keep track of insert and delete index paths
[super prepareForCollectionViewUpdates:updateItems];
self.deleteIndexPaths = [NSMutableArray array];
self.insertIndexPaths = [NSMutableArray array];
for (UICollectionViewUpdateItem *update in updateItems) {
if (update.updateAction == UICollectionUpdateActionDelete) {
[self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
}else if (update.updateAction == UICollectionUpdateActionInsert) {
[self.insertIndexPaths addObject:update.indexPathAfterUpdate];
}
}
}
// 更新結(jié)束后調(diào)用 這個(gè)方法在 performBatchUpdates:completion: complete的Block之前調(diào)用
- (void)finalizeCollectionViewUpdates {
[super finalizeCollectionViewUpdates];
// 釋放insert and delete index paths
self.deleteIndexPaths = nil;
self.insertIndexPaths = nil;
}
/**
這兩個(gè)方法是成對出現(xiàn)的,一個(gè)是在在屏幕上出現(xiàn)之前調(diào)用,一個(gè)是在屏幕上出現(xiàn)之后調(diào)用
在[collecView reloadData]或者performBatchUpdates:completion:調(diào)用的時(shí)候
只要是有刷新的效果,他們就會(huì)被調(diào)用多次,視圖不斷的消失(失效,被摧毀)出現(xiàn)(重組 被創(chuàng)建或者回收利用)
*/
// For each element on screen before the invalidation,
- (nullable UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
// Must call super
UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
if ([self.insertIndexPaths containsObject:itemIndexPath]) {
// 只改變插入的 attributes
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// configure attributes...
attributes.alpha = 0;
attributes.center = _center;
attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
}
return attributes;
}
// For each element on screen after the invalidation,
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
if ([self.deleteIndexPaths containsObject:itemIndexPath]) {
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
attributes.alpha = 0;
attributes.center = _center;
attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
}
return attributes;
}
UICollectionViewUpdateItem
這個(gè)類是UIcollectionView更新布局的時(shí)候,新舊布局信息交換的媒介,它存儲了舊的布局,或者新布局。
轉(zhuǎn)載我好朋友的一片文章,自己做了一部分的修改,挺詳細(xì)的,轉(zhuǎn)載出來大家分享一下。
