UICollectionView的布局是可以自己定義的,在這篇博客中先在上篇博客的基礎上進行擴充,我們先使用UICollectionViewFlowLayout,然后好好的介紹一下UICollectionView的一些回調方法,主要包括UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,UICollectionViewDelegate相關回調方法,並通過實例來介紹每個回調的用法。並且給每個Section添加定制的Header和Footer,好廢話少說進入今天的正題。
一、Demo總覽
下圖是本篇博客中Demo的最終運行效果,下面是我們要做的事情:
-
1. 給每個Section添加自定義的重用Header和Footer
-
2.調整第一個Section的上左下右的邊距( UIEdgeInsets )
-
3.給UICollectioinView設置多選
-
4.處理Cell的高亮事件
-
5.處理Cell的選中事件
-
6.調整Cell的上下左右邊距
-
7.對Cell進行編輯
二、UICollectionViewDataSource介紹
1、在UICollectionViewDataSource回調方法中有一個返回Section數量的方法,如下所示,該方法和UITableView中的用法一致。在這兒我們返回5個Section,如下所示:
1 #pragma mark <UICollectionViewDataSource> 2 3 /** 4 * 返回Section的個數 5 */ 6 - (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView { 7 return 5; 8 }
2、在UICollectionViewDataSource的回調方法中,還有一個是返回每個Section中Cell的數量的方法,在這我們返回30個Cell, 如下代碼所示:
1 /** 2 * 返回每個Section中Cell的個數 3 */ 4 - (NSInteger)collectionView: (UICollectionView *)collectionView 5 numberOfItemsInSection: (NSInteger)section { 6 7 return 30; 8 }
3、在UICollectionViewDataSource還有一個必須實現的方法, 就是選擇我們CollectionView中所使用的Cell, 在這里我們所使用的Cell是在Storyboard上實現的,所以不需要在我們的代碼中注冊Cell, 之間使用重用標示符就可以獲取Cell的對象,如下所示:
1 /** 2 * 返回Cell種類 3 */ 4 - (UICollectionViewCell *)collectionView: (UICollectionView *)collectionView 5 cellForItemAtIndexPath: (NSIndexPath *)indexPath { 6 7 //通過Cell重用標示符來獲取Cell 8 CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: reuseIdentifier 9 forIndexPath: indexPath]; 10 11 return cell; 12 }
4、在UICollectionViewDataSource方法中有一個可選的方法就是為我們的Section添加Supplementary View(追加視圖),下面是添Supplementary View(追加視圖)的步驟。在UICollectionView中的Section中我們可以為其增加Header View和Footer View, 也就是官方文檔上提到的Supplementary View(追加視圖)。追加視圖是可以重用的,也就是UICollectionReusableView。我們可以創建兩個UICollectionReusableView的子類,一個是Header View, 另一個是Footer View。
(1)創建UICollectionReusableView
追加視圖可以在Storyboard上添加,然后設置重用標示符,在代碼中使用即可。這里我們是從xib文件來加載的Supplementary View, 先創建兩個UICollectionReusableView子類,在創建該子類的同時創建相應的xib文件,如下所示:
創建Header View和Footer View的UICollectionReusableView,創建后的文件目錄如下:
(2) 因為我們是從xib文件中加載的UICollectionReusableView,所以需要在相應的UICollectionView上進行注冊。如果你是使用的Storyboard, 只需要在Storyboard中指定重用標示符即可。下面的代碼就是在ViewDidLoad中調用注冊UICollectionReusableView的方法。
1 /** 2 * 注冊Header和FooterView 3 * 便於在UICollectionViewDataSource中使用 4 */ 5 - (void) registerHeaderAndFooterView { 6 //注冊headerView 7 //獲取含有UICollectionReusableView的Nib文件。 8 UINib *headerNib = [UINib nibWithNibName: @"CollectionHeaderReusableView" 9 bundle: [NSBundle mainBundle]]; 10 11 //注冊重用View 12 [self.collectionView registerNib: headerNib 13 forSupplementaryViewOfKind: UICollectionElementKindSectionHeader 14 withReuseIdentifier: @"CollectionHeaderReusableView"]; 15 16 17 //注冊FooterView 18 UINib *footerNib = [UINib nibWithNibName: @"CollectionFooterReusableView" 19 bundle:[ NSBundle mainBundle]]; 20 21 [self.collectionView registerNib: footerNib 22 forSupplementaryViewOfKind: UICollectionElementKindSectionFooter 23 withReuseIdentifier: @"CollectionFooterReusableView"]; 24 25 }
(3)在UICollectionViewDataSource中的設置Supplementary View的方法中通過Header View和Footer View的重用標示符來為我們的Section設置Supplementary View,具體代碼如下所示:
1 /** 2 * 設置Setion的Header和Footer(Supplementary View) 3 */ 4 - (UICollectionReusableView *)collectionView: (UICollectionView *)collectionView 5 viewForSupplementaryElementOfKind: (NSString *)kind 6 atIndexPath: (NSIndexPath *)indexPath{ 7 8 //設置SectionHeader 9 if ([kind isEqualToString: UICollectionElementKindSectionHeader]) { 10 11 UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderReusableView" forIndexPath:indexPath]; 12 13 return view; 14 } 15 16 //設置SectionFooter 17 UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"CollectionFooterReusableView" forIndexPath:indexPath]; 18 return view; 19 20 }
UICollectionViewDataSource中的四個方法在上面都進行了實現,UICollectionViewDataSource主要是負責加載數據源的,包括Section的個數,每個Section中Cell的個數,每個Section中Supplementary View的種類。
三.UICollectionViewDelegateFlowLayout回調實現
UICollectionViewDelegateFlowLayout主要是負責顯示的,比如Secion的大小、邊距,Cell的大小邊距,headerView的大小已經FooterView的大小,都是在UICollectionViewDelegateFlowLayout的相應協議的方法來實現的。接下來詳細的介紹一下UICollectionViewDelegateFlowLayout協議中的方法。
1.同一個Section中同一種Cell(通過同一個Cell重用標示符獲取的對象)可以有不同的尺寸,下面的代碼是給Cell定制尺寸。代碼的具體意思是第一個Section中的所有Cell的尺寸是(50,50)。 其余的時(60,60)。
1 #pragma mark <UICollectionViewDelegateFlowLayout> 2 /** 3 * 改變Cell的尺寸 4 */ 5 - (CGSize)collectionView: (UICollectionView *)collectionView 6 layout: (UICollectionViewLayout*)collectionViewLayout 7 sizeForItemAtIndexPath: (NSIndexPath *)indexPath{ 8 9 if (indexPath.section == 0) { 10 return CGSizeMake(50, 50); 11 } 12 13 return CGSizeMake(60, 60); 14 }
2.改變Section的上下左右邊距--UIEdgeInsetsMake(上, 左, 下, 右),逆時針旋轉。第一個Section的上左下右的邊距都是50, 其余的Section上左下右的邊距是0。具體實現看如下代碼:
1 /** 2 * Section的上下左右邊距--UIEdgeInsetsMake(上, 左, 下, 右);逆時針 3 */ 4 - (UIEdgeInsets)collectionView: (UICollectionView *)collectionView 5 layout: (UICollectionViewLayout*)collectionViewLayout 6 insetForSectionAtIndex: (NSInteger)section{ 7 8 if (section == 0) { 9 return UIEdgeInsetsMake(50, 50, 50, 50); 10 } 11 return UIEdgeInsetsMake(0, 0, 0, 0); 12 }
3.設置每個Cell的上下邊距的回調如下所示,第一個Section的Cell上下邊距是5.0f, 其余的為20.0f。
1 /** 2 * Section中每個Cell的上下邊距 3 */ 4 - (CGFloat)collectionView: (UICollectionView *)collectionView 5 layout: (UICollectionViewLayout*)collectionViewLayout 6 minimumLineSpacingForSectionAtIndex: (NSInteger)section{ 7 if (section == 0) { 8 return 5.0f; 9 } 10 return 20.0f; 11 }
4.設置Cell的左右邊距,第一個Section的Cell左右邊距是5.0f, 其余的為20.0f。
1 /** 2 * Section中每個Cell的左右邊距 3 */ 4 - (CGFloat)collectionView: (UICollectionView *)collectionView 5 layout: (UICollectionViewLayout*)collectionViewLayout 6 minimumInteritemSpacingForSectionAtIndex: (NSInteger)section{ 7 if (section == 0) { 8 return 5.0f; 9 } 10 return 20.0f; 11 }
5.設置Header View和Footer View的大小的回調如下。
1 /** 2 * headerView的大小 3 */ 4 - (CGSize)collectionView: (UICollectionView *)collectionView 5 layout: (UICollectionViewLayout*)collectionViewLayout 6 referenceSizeForHeaderInSection: (NSInteger)section{ 7 return CGSizeMake(200, 50); 8 } 9 10 /** 11 * footerView的大小 12 */ 13 - (CGSize)collectionView: (UICollectionView *)collectionView 14 layout: (UICollectionViewLayout*)collectionViewLayout 15 referenceSizeForFooterInSection: (NSInteger)section{ 16 return CGSizeMake(200, 50); 17 }
上面的方法就是UICollectionViewDelegateFlowLayout中所有的方法了,負責布局顯示的。
四、UICollectionViewDelegate回調實現
UICollectionViewDelegate中的代理方法主要是負責Cell的交互的,比如是否高亮,是否選,是否可編輯等,接下來要為大家詳細的介紹UICollectionViewDelegate中的代理方法。
1.為了這部分的效果展示,我們需要對Cell添加一些控件,並且設置其Highlight和Selected的一些狀態。為Cell添加上ImageView, Cell的高亮狀態和非高亮狀態對應的ImageView上的圖片是不同的。再添加一個Button, 並為Button設置Selected和Default狀態下的圖片,Button的選中和默認狀態由Cell的選中狀態來定。Cell中改變ImageView的圖片的代碼如下所示,函數傳入的參數是當前Cell的高亮狀態,根據高亮狀態來設置ImageView上的Image。(有的小伙伴會問為什么給ImageView在Default狀態和Highlight下設置不同的圖片,然后直接改變ImageView的高亮狀態即可。你可以試一下,達不到預期的效果)
1 - (void) changeHighLightWithBool: (BOOL) highlight{ 2 3 NSString *imageName = @"003.jpg"; 4 5 if (highlight) { 6 imageName = @"002.jpg"; 7 } 8 9 [_highlightImage setImage: [UIImage imageNamed:imageName]]; 10 }
2.設置Cell可以高亮, 返回YES代表Cell可以高亮,返回NO代表Cell不可高亮。高亮就是觸摸Cell時該Cell變為高亮狀態,在代碼中的反應就是Cell的Highligth屬性變為YES。而觸摸結束時,Cell的Highligth屬性就變為NO。
1 #pragma mark <UICollectionViewDelegate> 2 3 /** 4 * Cell是否可以高亮 5 */ 6 - (BOOL)collectionView: (UICollectionView *)collectionView 7 shouldHighlightItemAtIndexPath: (NSIndexPath *)indexPath{ 8 9 return YES; 10 11 }
3.下面這個方法是自己寫的,用來在界面上反應Cell的高亮狀態。 ImageView在當前Cell高亮狀態下和非高亮狀態下所加載的圖片不同,所以可以看出Cell高亮和非高亮。
1 /** 2 * 根據高亮狀態修改背景圖片 3 */ 4 - (void) changeHighlightCellWithIndexPaht: (NSIndexPath *) indexPath{ 5 //獲取當前變化的Cell 6 CollectionViewCell *currentHighlightCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; 7 8 [currentHighlightCell changeHighLightWithBool:currentHighlightCell.highlighted]; 9 10 if (currentHighlightCell.highlighted == YES){ 11 12 NSLog(@"第%ld個Section上第%ld個Cell變為高亮",indexPath.section ,indexPath.row); 13 return; 14 } 15 16 if (currentHighlightCell.highlighted == NO){ 17 NSLog(@"第%ld個Section上第%ld個Cell變為非高亮",indexPath.section ,indexPath.row); 18 } 19 20 21 }
4.Cell從非高亮變為高亮狀態時回調用下面的方法,為了反映Cell的高亮狀態,我們去改變一下Cell上ImageView的圖片。
1 /** 2 * 如果Cell可以高亮,Cell變為高亮后調用該方法 3 */ 4 - (void)collectionView: (UICollectionView *)collectionView 5 didHighlightItemAtIndexPath: (NSIndexPath *)indexPath{ 6 7 [self changeHighlightCellWithIndexPath:indexPath]; 8 } 9 10 11 /** 12 * 如果Cell可以高亮,Cell從高亮變為非高亮調用該方法 13 */ 14 - (void)collectionView: (UICollectionView *)collectionView 15 didUnhighlightItemAtIndexPath: (NSIndexPath *)indexPath{ 16 17 [self changeHighlightCellWithIndexPath:indexPath]; 18 19 }
5.設定Cell是否可選的回調如下所示,Cell被選中時該Cell的Selected為YES, 取消選中Selected為NO;
1 /** 2 * Cell是否可以選中 3 */ 4 - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 5 return YES; 6 }
6. 如果想讓你的Cell支持多選,就需要設定一下CollectionView的allowsMultipleSelection屬性,下面的代碼是在ViewDidLoad中添加的,如下所示:
1 //設置Cell多選 2 self.collectionView.allowsMultipleSelection = YES;
7.如果在多選狀態下需要支持取消Cell的多選,那么就去執行下面的方法,並返回YES。就是支持在多選狀態下取消選中狀態。
1 /** 2 * Cell多選時是否支持取消功能 3 */ 4 - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath{ 5 return YES; 6 }
8.下面這個方法是自己封裝的,用來根據Cell的選中狀態來改變Cell上Button的選中狀態,具體代碼實現如下:
1 /** 2 * Cell根據Cell選中狀態來改變Cell上Button按鈕的狀態 3 */ 4 - (void) changeSelectStateWithIndexPath: (NSIndexPath *) indexPath{ 5 //獲取當前變化的Cell 6 CollectionViewCell *currentSelecteCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; 7 8 currentSelecteCell.selectButton.selected = currentSelecteCell.selected; 9 10 if (currentSelecteCell.selected == YES){ 11 NSLog(@"第%ld個Section上第%ld個Cell被選中了",indexPath.section ,indexPath.row); 12 return; 13 } 14 15 if (currentSelecteCell.selected == NO){ 16 //NSLog(@"第%ld個Section上第%ld個Cell取消選中",indexPath.section ,indexPath.row); 17 } 18 19 }
9.在Cell選中和取消選中時都會調用上面的方法來改變Button的選中狀態,下面是Cell在選中時以及取消選中時所調用的方法:
1 /** 2 * Cell選中調用該方法 3 */ 4 - (void)collectionView: (UICollectionView *)collectionView 5 didSelectItemAtIndexPath: (NSIndexPath *)indexPath{ 6 7 [self changeSelectStateWithIndexPath:indexPath]; 8 } 9 10 /** 11 * Cell取消選中調用該方法 12 */ 13 - (void)collectionView: (UICollectionView *)collectionView didDeselectItemAtIndexPath: (NSIndexPath *)indexPath{ 14 15 [self changeSelectStateWithIndexPath:indexPath]; 16 }
10.下方四個方法是Cell將要出現,Cell出現后,Supplementary View將要出現以及Supplementary View已經出現所調用的方法,具體信息請看下方代碼實現:
1 /** 2 * Cell將要出現的時候調用該方法 3 */ 4 - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0){ 5 NSLog(@"第%ld個Section上第%ld個Cell將要出現",indexPath.section ,indexPath.row); 6 } 7 8 /** 9 * Cell出現后調用該方法 10 */ 11 - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{ 12 NSLog(@"第%ld個Section上第%ld個Cell已經出現",indexPath.section ,indexPath.row); 13 } 14 15 16 /** 17 * headerView或者footerView將要出現的時候調用該方法 18 */ 19 - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0){ 20 21 NSLog(@"第%ld個Section上第%ld個擴展View將要出現",indexPath.section ,indexPath.row); 22 23 } 24 25 /** 26 * headerView或者footerView出現后調用該方法 27 */ 28 - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath{ 29 30 NSLog(@"第%ld個Section上第%ld個擴展View已經出現",indexPath.section ,indexPath.row); 31 32 }
在UICollectionViewDelegate回調方法中還有三個回調方法是關於Cell編輯的,比如copy, past, cut等操作,具體代碼就不在此贅述了。在Demo中給出了實現方式,主要涉及到UIPasteboard的操作,本篇博客的整體的Demo回分享到Github上,下方是Github上的分享鏈接,感興趣的小伙伴可以進行Clone。
Github分享鏈接:https://github.com/lizelu/CollectionViewControllerDemo