作者:liuchungui
實現垂直方向的單列表來說,使用UITableView足以;若是需要構建橫向滑動列表、gridView等直線型布局,則使用UICollectionView+UICollectionViewFlowLayout搭建最合適;更復雜的布局,則可以使用UICollectionView+自定義Layout來實現。
而這篇博客就來介紹一下UICollectionView。
首先,來了解一下UICollectionView工作流程:
當UICollectionView顯示內容時,先從數據源獲取cell,然後交給UICollectionView。再從UICollectionViewLayout獲取對應的layout attributes(布局屬性)。最後,根據每個cell對應的layout attributes(布局屬性)來對cell進行布局,生成了最終的界面。而用戶交互的時候,都是通過Delegate來進行交互。當然,上面只是布局cell,但是UICollectionView內部還有Supplementary View和Decoration View,也可以對其進行布局。
上面,我們了解了UICollectionView的工作流程,我們將UICollectionView分成視圖、數據源和代理方法、UICollectionViewLayout三塊來介紹。
一、視圖
UICollectionView上面顯示內容的視圖有三種Cell視圖、Supplementary View和Decoration View。
Cell視圖
CollectionView中主要的內容都是由它展示的,它是從數據源對象獲取的。
Supplementary View
它展示了每一組當中的信息,與cell類似,它是從數據源方法當中獲取的,但是與cell不同的是,它並不是強制需要的。例如flow layout當中的headers和footers就是可選的Supplementary View。
Decoration View
這個視圖是一個裝飾視圖,它沒有什麼功能性,它不跟數據源有任何關系,它完全屬於layout對象。
二、數據源和代理方法
1、注冊cell或者Supplementary View使其重用
在使用數據源返回cell或者Supplementary View給collectionView之前,我們必須先要注冊,用來進行重用。
registerClass: forCellWithReuseIdentifier:
registerNib: forCellWithReuseIdentifier:
registerClass: forSupplementaryViewOfKind: withReuseIdentifier:
registerNib: forSupplementaryViewOfKind: withReuseIdentifier:
顯而易見,前面兩個方法是注冊cell,後兩個方法注冊Supplementary View。其中,注冊的方式有兩種,第一種是直接注冊class,它重用的時候會調用[[UICollectionView alloc] init]這樣的初始化方法創建cell;另外一種是注冊nib,它會自動加載nib文件。
注冊的之後,我們如何重用?
在數據源方法當中返回cell或者Supplementary view的方法當中通過dequeueReusableCellWithReuseIdentifier:forIndexPath: 或者 dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法獲取cell或者Supplementary View。
示例代碼:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ CollectionViewCell *cell = (CollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellReuseIdentify forIndexPath:indexPath]; cell.backgroundColor = [UIColor lightGrayColor]; cell.textLabel.text = [NSString stringWithFormat:@"(%zd,%zd)", indexPath.section, indexPath.row]; return cell; }
2、數據源方法
數據源方法與UITableView類似,主要有:
numberOfSectionsInCollectionView:
collectionView: numberOfItemsInSection:
collectionView: cellForItemAtIndexPath:
collectionView: viewForSupplementaryElementOfKind: atIndexPath:
與UITableView不同的是多加了返回Supplementary view數據源方法。
3、代理方法
數據源為UICollectionView提供數據相關的內容,而代理則主要負責用戶交互、與數據無關的視圖外形。主要分成兩部分:
1、通過調用代理方法,管理視圖的選中、高亮
collectionView:shouldDeselectItemAtIndexPath:
collectionView:didSelectItemAtIndexPath:
collectionView:didDeselectItemAtIndexPath:
collectionView:shouldHighlightItemAtIndexPath:
collectionView:didHighlightItemAtIndexPath:
collectionView:didUnhighlightItemAtIndexPath:
2、長按cell,顯示編輯菜單 與UITableView不同,用戶長按cell時,UICollectionView可以顯示編輯菜單。這個編輯菜單可以用來剪切、復制和粘貼cell。不過,要顯示這個編輯菜單需要滿足下面幾個條件:
代理對象必須實現下面三個方法:
collectionView:shouldShowMenuForItemAtIndexPath:
collectionView:canPerformAction:forItemAtIndexPath:withSender:
collectionView:performAction:forItemAtIndexPath:withSender:
對於指定要編輯的cell,collectionView:shouldShowMenuForItemAtIndexPath:方法需要返回YES
collectionView:canPerformAction:forItemAtIndexPath:withSender: 方法中,對於剪切、復制、粘貼三種action至少有一個返回YES。其實,編輯菜單是有很多種action的,但是對於UICollectionView來說,它僅僅支持的剪切、復制、粘貼三個,所以說這個代理方法至少支持這三種的一種。
剪切、復制、粘貼的方法名是:
cut:
copy:
paste:
當上面的條件都滿足了,用戶就可以長按cell顯示出編輯菜單,然後選擇對應的action,從而就會回調delegate的collectionView:performAction:forItemAtIndexPath:withSender: 方法去做對應的事情。
當我們想控制編輯菜單僅僅顯示復制和粘貼時,我們就可以在collectionView:canPerformAction:forItemAtIndexPath:withSender:方法中進行操作,具體請見下面代碼:
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender{ if ([NSStringFromSelector(action) isEqualToString:@"copy:"] || [NSStringFromSelector(action) isEqualToString:@"paste:"]) return YES; return NO; }
三、UICollectionViewLayout
UICollectionViewLayout是通過UICollectionViewLayoutAttributes類來管理cell、Supplementary View和Decoration View的位置、transform、alpha、hidden等等。
UICollectionViewLayout這個類只是一個基類,我們給UICollectionView使用的都是它的子類。系統為我們提供了一個最常用的layout為UICollectionViewFlowLayout,我們可以使用它制作grid view。當UICollectionViewLayout滿足不了我們的需求時,我們可以子類化UICollectionViewLayout或者自定義layout,這個內容放到我下一篇當中。
UICollectionViewFlowLayout
使用UICollectionViewFlowLayout之前,我們來了解它內部常用的屬性:
//同一組當中,垂直方向:行與行之間的間距;水平方向:列與列之間的間距 @property (nonatomic) CGFloat minimumLineSpacing; //垂直方向:同一行中的cell之間的間距;水平方向:同一列中,cell與cell之間的間距 @property (nonatomic) CGFloat minimumInteritemSpacing; //每個cell統一尺寸 @property (nonatomic) CGSize itemSize; //滑動反向,默認滑動方向是垂直方向滑動 @property (nonatomic) UICollectionViewScrollDirection scrollDirection; //每一組頭視圖的尺寸。如果是垂直方向滑動,則只有高起作用;如果是水平方向滑動,則只有寬起作用。 @property (nonatomic) CGSize headerReferenceSize; //每一組尾部視圖的尺寸。如果是垂直方向滑動,則只有高起作用;如果是水平方向滑動,則只有寬起作用。 @property (nonatomic) CGSize footerReferenceSize; //每一組的內容縮進 @property (nonatomic) UIEdgeInsets sectionInset;
注意:UICollectionViewFlowLayout內部的屬性都是用來統一設置,若是統一設置無法滿足需求,可以實現UICollectionViewDelegateFlowLayout代理方法,進行對應的設置。而後面內容我都以UICollectionViewFlowLayout的屬性來敘述,請自行參照修改。
UICollectionViewFlowLayout在縱向滑動與橫向滑動時,布局是不太一樣的。
由上圖就可以看出來,UICollectionViewFlowLayout在布局時,會根據scrollDirection的值不同而產生不同的布局。
垂直方向滑動:
Cell布局:UICollectionView的內容寬度與本身視圖的寬度相等,並且是固定的。會根據sectionInset左右縮進、itemSize的寬度、minimumInteritemSpacing三個值來計算每一行cell數量。
具體計算公式是:
cellCount = (CollectionViewContentWidth-sectionInset.left-sectionInset.right+minimumInteritemSpacing)/(itemSize.width+minimumInteritemSpacing)CollectionViewContentWidth是UICollectionView的內容寬度,計算出來的cellCount進行四捨五入成一個整數就是每一行cell的數量。
而每個cell之間實際的間隔值則是:
realInteritemSpacing = (CollectionViewContentWidth-sectionInset.left-sectionInset.right-cellCount*itemSize.width)/(cellCount-1)
當每個cell大小確定、每一行cell的個數確定、每個cell之間的間距確定之後,UICollectionViewFlowLayout就可以計算出每一行cell的frame了。
而如果同一組cell的個數,在水平方向的一行放不下去,則就會放入第二行,而這第二行的cell在垂直方向與第一行的cell相隔minimumLineSpacing。這樣又確定了行與行之間的間距,那麼這一組cell的布局就可以確定了。
頭視圖與尾部視圖:根據headerReferenceSize和footerReferenceSize中的高來確定頭部和尾部視圖的高,它其實就是兩個不同類型的Supplementary View。
水平方向滑動:
Cell布局:水平方向的滑動內容的高與本身視圖的高是相等的,並且是固定的。它的cell是從上到下進行布局的。會根據sectionInset上下縮進、itemSize的高度、minimumInteritemSpacing三個值來計算每一列放多少個cell,具體計算公式可以參照垂直方向滑動的公式。之後的邏輯和垂直方向滑動時一樣,只是minimumLineSpacing現在是代表列與列之間的間距。
頭視圖與尾部視圖:根據headerReferenceSize和footerReferenceSize中的寬來確定頭部和尾部視圖的寬。
相關使用UICollectionViewFlowLayout代碼:UICollectionViewDemo
參考
Collection View Programming Guide for iOS
WWDC 2012 Session筆記——205 Introducing Collection Views