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 2 3 4 5 6 7 8 |
#pragma mark /** * 返回Section的個數 */ - (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView { return 5; } |
2、在UICollectionViewDataSource的回撥方法中,還有一個是返回每個Section中Cell的數量的方法,在這我們返回30個Cell, 如下程式碼所示:
1 2 3 4 5 6 7 8 |
/** * 返回每個Section中Cell的個數 */ - (NSInteger)collectionView: (UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section { return 30; } |
3、在UICollectionViewDataSource還有一個必須實現的方法, 就是選擇我們CollectionView中所使用的Cell, 在這裡我們所使用的Cell是在Storyboard上實現的,所以不需要在我們的程式碼中註冊Cell, 之間使用重用標示符就可以獲取Cell的物件,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 返回Cell種類 */ - (UICollectionViewCell *)collectionView: (UICollectionView *)collectionView cellForItemAtIndexPath: (NSIndexPath *)indexPath { //通過Cell重用標示符來獲取Cell CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: reuseIdentifier forIndexPath: indexPath]; return cell; } |
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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * 註冊Header和FooterView * 便於在UICollectionViewDataSource中使用 */ - (void) registerHeaderAndFooterView { //註冊headerView //獲取含有UICollectionReusableView的Nib檔案。 UINib *headerNib = [UINib nibWithNibName: @"CollectionHeaderReusableView" bundle: [NSBundle mainBundle]]; //註冊重用View [self.collectionView registerNib: headerNib forSupplementaryViewOfKind: UICollectionElementKindSectionHeader withReuseIdentifier: @"CollectionHeaderReusableView"]; //註冊FooterView UINib *footerNib = [UINib nibWithNibName: @"CollectionFooterReusableView" bundle:[ NSBundle mainBundle]]; [self.collectionView registerNib: footerNib forSupplementaryViewOfKind: UICollectionElementKindSectionFooter withReuseIdentifier: @"CollectionFooterReusableView"]; } |
(3)在UICollectionViewDataSource中的設定Supplementary View的方法中通過Header View和Footer View的重用標示符來為我們的Section設定Supplementary View,具體程式碼如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * 設定Setion的Header和Footer(Supplementary View) */ - (UICollectionReusableView *)collectionView: (UICollectionView *)collectionView viewForSupplementaryElementOfKind: (NSString *)kind atIndexPath: (NSIndexPath *)indexPath{ //設定SectionHeader if ([kind isEqualToString: UICollectionElementKindSectionHeader]) { UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"CollectionHeaderReusableView" forIndexPath:indexPath]; return view; } //設定SectionFooter UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"CollectionFooterReusableView" forIndexPath:indexPath]; return view; } |
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 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#pragma mark /** * 改變Cell的尺寸 */ - (CGSize)collectionView: (UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath: (NSIndexPath *)indexPath{ if (indexPath.section == 0) { return CGSizeMake(50, 50); } return CGSizeMake(60, 60); } |
2.改變Section的上下左右邊距–UIEdgeInsetsMake(上, 左, 下, 右),逆時針旋轉。第一個Section的上左下右的邊距都是50, 其餘的Section上左下右的邊距是0。具體實現看如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * Section的上下左右邊距--UIEdgeInsetsMake(上, 左, 下, 右);逆時針 */ - (UIEdgeInsets)collectionView: (UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex: (NSInteger)section{ if (section == 0) { return UIEdgeInsetsMake(50, 50, 50, 50); } return UIEdgeInsetsMake(0, 0, 0, 0); } |
3.設定每個Cell的上下邊距的回撥如下所示,第一個Section的Cell上下邊距是5.0f, 其餘的為20.0f。
1 2 3 4 5 6 7 8 9 10 11 |
/** * Section中每個Cell的上下邊距 */ - (CGFloat)collectionView: (UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex: (NSInteger)section{ if (section == 0) { return 5.0f; } return 20.0f; } |
4.設定Cell的左右邊距,第一個Section的Cell左右邊距是5.0f, 其餘的為20.0f。
1 2 3 4 5 6 7 8 9 10 11 |
/** * Section中每個Cell的左右邊距 */ - (CGFloat)collectionView: (UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex: (NSInteger)section{ if (section == 0) { return 5.0f; } return 20.0f; } |
5.設定Header View和Footer View的大小的回撥如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * headerView的大小 */ - (CGSize)collectionView: (UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection: (NSInteger)section{ return CGSizeMake(200, 50); } /** * footerView的大小 */ - (CGSize)collectionView: (UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection: (NSInteger)section{ return CGSizeMake(200, 50); } |
上面的方法就是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 2 3 4 5 6 7 8 9 10 |
- (void) changeHighLightWithBool: (BOOL) highlight{ NSString *imageName = @"003.jpg"; if (highlight) { imageName = @"002.jpg"; } [_highlightImage setImage: [UIImage imageNamed:imageName]]; } |
2.設定Cell可以高亮, 返回YES代表Cell可以高亮,返回NO代表Cell不可高亮。高亮就是觸控Cell時該Cell變為高亮狀態,在程式碼中的反應就是Cell的Highligth屬性變為YES。而觸控結束時,Cell的Highligth屬性就變為NO。
1 2 3 4 5 6 7 8 9 10 11 |
#pragma mark /** * Cell是否可以高亮 */ - (BOOL)collectionView: (UICollectionView *)collectionView shouldHighlightItemAtIndexPath: (NSIndexPath *)indexPath{ return YES; } |
3.下面這個方法是自己寫的,用來在介面上反應Cell的高亮狀態。 ImageView在當前Cell高亮狀態下和非高亮狀態下所載入的圖片不同,所以可以看出Cell高亮和非高亮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * 根據高亮狀態修改背景圖片 */ - (void) changeHighlightCellWithIndexPaht: (NSIndexPath *) indexPath{ //獲取當前變化的Cell CollectionViewCell *currentHighlightCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; [currentHighlightCell changeHighLightWithBool:currentHighlightCell.highlighted]; if (currentHighlightCell.highlighted == YES){ NSLog(@"第%ld個Section上第%ld個Cell變為高亮",indexPath.section ,indexPath.row); return; } if (currentHighlightCell.highlighted == NO){ NSLog(@"第%ld個Section上第%ld個Cell變為非高亮",indexPath.section ,indexPath.row); } } |
4.Cell從非高亮變為高亮狀態時回撥用下面的方法,為了反映Cell的高亮狀態,我們去改變一下Cell上ImageView的圖片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * 如果Cell可以高亮,Cell變為高亮後呼叫該方法 */ - (void)collectionView: (UICollectionView *)collectionView didHighlightItemAtIndexPath: (NSIndexPath *)indexPath{ [self changeHighlightCellWithIndexPath:indexPath]; } /** * 如果Cell可以高亮,Cell從高亮變為非高亮呼叫該方法 */ - (void)collectionView: (UICollectionView *)collectionView didUnhighlightItemAtIndexPath: (NSIndexPath *)indexPath{ [self changeHighlightCellWithIndexPath:indexPath]; } |
5.設定Cell是否可選的回撥如下所示,Cell被選中時該Cell的Selected為YES, 取消選中Selected為NO;
1 2 3 4 5 6 |
/** * Cell是否可以選中 */ - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{ return YES; } |
6. 如果想讓你的Cell支援多選,就需要設定一下CollectionView的allowsMultipleSelection屬性,下面的程式碼是在ViewDidLoad中新增的,如下所示:
1 2 |
//設定Cell多選 self.collectionView.allowsMultipleSelection = YES; |
7.如果在多選狀態下需要支援取消Cell的多選,那麼就去執行下面的方法,並返回YES。就是支援在多選狀態下取消選中狀態。
1 2 3 4 5 6 |
/** * Cell多選時是否支援取消功能 */ - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath{ return YES; } |
8.下面這個方法是自己封裝的,用來根據Cell的選中狀態來改變Cell上Button的選中狀態,具體程式碼實現如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * Cell根據Cell選中狀態來改變Cell上Button按鈕的狀態 */ - (void) changeSelectStateWithIndexPath: (NSIndexPath *) indexPath{ //獲取當前變化的Cell CollectionViewCell *currentSelecteCell = (CollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; currentSelecteCell.selectButton.selected = currentSelecteCell.selected; if (currentSelecteCell.selected == YES){ NSLog(@"第%ld個Section上第%ld個Cell被選中了",indexPath.section ,indexPath.row); return; } if (currentSelecteCell.selected == NO){ //NSLog(@"第%ld個Section上第%ld個Cell取消選中",indexPath.section ,indexPath.row); } } |
9.在Cell選中和取消選中時都會呼叫上面的方法來改變Button的選中狀態,下面是Cell在選中時以及取消選中時所呼叫的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/** * Cell選中呼叫該方法 */ - (void)collectionView: (UICollectionView *)collectionView didSelectItemAtIndexPath: (NSIndexPath *)indexPath{ [self changeSelectStateWithIndexPath:indexPath]; } /** * Cell取消選中呼叫該方法 */ - (void)collectionView: (UICollectionView *)collectionView didDeselectItemAtIndexPath: (NSIndexPath *)indexPath{ [self changeSelectStateWithIndexPath:indexPath]; } |
10.下方四個方法是Cell將要出現,Cell出現後,Supplementary View將要出現以及Supplementary View已經出現所呼叫的方法,具體資訊請看下方程式碼實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * Cell將要出現的時候呼叫該方法 */ - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0){ NSLog(@"第%ld個Section上第%ld個Cell將要出現",indexPath.section ,indexPath.row); } /** * Cell出現後呼叫該方法 */ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{ NSLog(@"第%ld個Section上第%ld個Cell已經出現",indexPath.section ,indexPath.row); } /** * headerView或者footerView將要出現的時候呼叫該方法 */ - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0){ NSLog(@"第%ld個Section上第%ld個擴充套件View將要出現",indexPath.section ,indexPath.row); } /** * headerView或者footerView出現後呼叫該方法 */ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath{ NSLog(@"第%ld個Section上第%ld個擴充套件View已經出現",indexPath.section ,indexPath.row); } |
在UICollectionViewDelegate回撥方法中還有三個回撥方法是關於Cell編輯的,比如copy, past, cut等操作,具體程式碼就不在此贅述了。在Demo中給出了實現方式,主要涉及到UIPasteboard的操作,本篇部落格的整體的Demo回分享到Github上,下方是Github上的分享連結,感興趣的小夥伴可以進行Clone。
Github分享連結:https://github.com/lizelu/CollectionViewControllerDemo
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!