UICollectionView是我們常說的集合視圖,它在iOS 6中引入,是iOS開發(fā)者中最受歡迎的UI元素之一。其布局靈活、可變,可用于顯示有序數(shù)據(jù)項(xiàng)集,最常見的用途是以類似于網(wǎng)格的形式呈現(xiàn)item,除此之外還可以通過子類化UICollectionViewLayout類,精準(zhǔn)地控制可視化元素布局,并動(dòng)態(tài)改變布局。因此,可以實(shí)現(xiàn)網(wǎng)格、堆棧、圓形、動(dòng)態(tài)變化等形式布局,以及其它任何你可以想象出的布局。

UICollectionView將數(shù)據(jù)源和用于呈現(xiàn)數(shù)據(jù)的視覺元素進(jìn)行了嚴(yán)格的分離。下圖顯示了UICollectionView與相關(guān)對(duì)象關(guān)系:

其中,data source提供用于呈現(xiàn)數(shù)據(jù)的視圖對(duì)象,collection view layout提供視圖布局信息,而collection view負(fù)責(zé)將數(shù)據(jù)和布局信息合并后呈現(xiàn)到屏幕上。需要注意的是,在創(chuàng)建UICollectionView時(shí),必須傳遞一個(gè)UICollectionViewLayout對(duì)象,這里的UICollectionViewLayout是一個(gè)抽象基類abstract base class,不能直接使用,必須使用其子類。例如,在創(chuàng)建網(wǎng)格布局時(shí)一般使用UICollectionViewFlowLayout具體concrete類。
下面表格列出了UIKit中與集合視圖相關(guān)的類,并按照各自扮演的角色進(jìn)行分類:
| 用途 | 類/協(xié)議 | 描述 |
|---|---|---|
| 集合視圖和集合視圖控制器 |
UICollectionView UICollectionViewController
|
UICollectionView派生自UIScrollView,定義集合視圖內(nèi)容區(qū)域,將dataSource的數(shù)據(jù)與layout提供的布局信息合并后呈現(xiàn)到屏幕上。 UICollectionViewController為集合視圖提供了控制器級(jí)別支持,UICollectionViewController的使用是可選的。 |
| 內(nèi)容控制 |
UICollectionViewDataSource協(xié)議 UICollectionViewDelegate協(xié)議 |
dataSource為集合視圖提供數(shù)據(jù),是UICollectionView中最重要、必須提供的對(duì)象。要實(shí)現(xiàn)dataSource中的方法,必須創(chuàng)建一個(gè)遵守UICollectionViewDataSource協(xié)議的對(duì)象。通過 UICollectionView的delegate對(duì)象可以監(jiān)聽集合視圖狀態(tài)、自定義視圖。例如,使用delegate跟蹤item是否高亮、選中。與數(shù)據(jù)源對(duì)象不同,代理對(duì)象不是必須實(shí)現(xiàn)。 |
| 呈現(xiàn)視圖 |
UICollectionReusableView UICollectionViewCell
|
UICollectionView中顯示的所有視圖都必須是UICollectionReusableView類的實(shí)例,該類支持回收機(jī)制(循環(huán)使用視圖,而非創(chuàng)建新的視圖),以便提高性能,特別是在滑動(dòng)屏幕時(shí)。UICollectionViewCell用來顯示主要數(shù)據(jù),也是可重用視圖。 |
| 布局 |
UICollectionViewLayout UICollectionViewLayoutAttributes UICollectionViewUpdateItem
|
使用UICollectionViewLayout的子類為集合視圖內(nèi)元素提供位置、大小、視覺屬性等布局信息。 在布局過程中, layout對(duì)象創(chuàng)建UICollectionViewLayoutAttributes實(shí)例,用以告知特定item如何布局。 當(dāng)collection view的數(shù)據(jù)源發(fā)生插入、刪除、移動(dòng)變化時(shí), UICollectionView會(huì)創(chuàng)建UICollectionViewUpdateItem類的實(shí)例,并發(fā)送給layout的prepareForCollectionViewUpdates:方法,layout會(huì)為即將到來的布局變化作出準(zhǔn)備。你不需要?jiǎng)?chuàng)建該類的實(shí)例。 |
| Flow layout |
UICollectionViewFlowLayout UICollectionViewDelegateFlowLayout協(xié)議 |
UICollectionViewFlowLayout類是用于實(shí)現(xiàn)網(wǎng)格或其它基于行布局的具體類,可以直接使用,也可以將其與UICollectionViewDelegateFlowLayout代理結(jié)合使用,以便自定義布局。 |
注意:上面的
UICollectionViewLayout、UICollectionViewReusableView類必須子類化才可以使用,其它類可以直接使用。
另外,UICollectionView自iOS 6引入以來,其功能也是不斷豐富的:
- iOS 9中為集合視圖添加了交互式重新排序功能。
- iOS 10中為集合視圖添加了預(yù)加載cell數(shù)據(jù)功能,這在獲取cell內(nèi)容非常耗時(shí)(例如網(wǎng)絡(luò)請(qǐng)求)的情況下非常有用。
- iOS 11增加了系統(tǒng)范圍的拖放操作drag and drop,讓用戶可以快速簡(jiǎn)單的將文本、圖像和文件從一個(gè)app移動(dòng)到另一個(gè)app。
現(xiàn)在我們就通過這篇文章,對(duì)UICollectionView進(jìn)行全面的學(xué)習(xí)。
1.創(chuàng)建demo
這篇文章將使用純代碼創(chuàng)建一個(gè)UICollectionView,用來學(xué)習(xí)集合視圖。效果如下:

打開Xcode,點(diǎn)擊File > New > Project...,選擇iOS > Application > Single View App模板,點(diǎn)擊Next;Product Name為CollectionView,Language為Objective-C,點(diǎn)擊Next;選擇文件位置,點(diǎn)擊Create創(chuàng)建工程。
2.添加UICollectionView
為視圖控制器添加UICollectonView,進(jìn)入ViewController.m,在接口部分添加以下聲明:
@interface ViewController ()
@property (strong, nonatomic) UICollectionView *collectionView;
@property (strong, nonatomic) UICollectionViewFlowLayout *flowLayout;
@end
在實(shí)現(xiàn)部分初始化UICollectionViewFlowLayout、UICollectionView對(duì)象。
- (UICollectionViewFlowLayout *)flowLayout {
if (!_flowLayout) {
// 初始化UICollectionViewFlowLayout對(duì)象,設(shè)置集合視圖滑動(dòng)方向。
_flowLayout = [[UICollectionViewFlowLayout alloc] init];
_flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
}
return _flowLayout;
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
// 設(shè)置集合視圖內(nèi)容區(qū)域、layout、背景顏色。
_collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.flowLayout];
_collectionView.backgroundColor = [UIColor whiteColor];
// 設(shè)置代理。
// _collectionView.dataSource = self;
// _collectionView.delegate = self;
}
return _collectionView;
}
最后添加self.collectionView到視圖控制器。
- (void)viewDidLoad {
[super viewDidLoad];
// 添加collection view。
[self.view addSubview:self.collectionView];
}
3.重用視圖以提高性能
UICollectionView使用了視圖回收機(jī)制以提高性能。當(dāng)視圖被滑出屏幕外時(shí),從視圖層級(jí)結(jié)構(gòu)中移除的視圖不會(huì)直接刪除,而是置于重用隊(duì)列中。當(dāng)UICollectionView顯示新的內(nèi)容時(shí),將從重用隊(duì)列中獲取視圖、填充新的內(nèi)容。為便于回收和重用,UICollectionView顯示的所有視圖必須派生自UICollectionReusableView。
UICollectionView支持三種不同類型的可重用視圖,每種視圖都有特定的用途:
-
集合視圖單元格UICollectionViewCell:顯示集合視圖的主要內(nèi)容。cell必須是
UICollectionViewCell類的實(shí)例。cell默認(rèn)支持管理自身高亮highlight、選中selection狀態(tài)。 -
補(bǔ)充視圖Supplementary View:顯示關(guān)于
section的信息。和cell一樣supplementary view也是數(shù)據(jù)驅(qū)動(dòng)的,但與cell不同的是supplementary view的使用不是必須的,layout控制supplementary view的位置和是否使用。例如,流式布局UICollectionViewFlowLayout可以選擇性添加頁(yè)眉section header和頁(yè)腳section footer補(bǔ)充視圖。 -
裝飾視圖Decoration View:由
layout完全擁有的裝飾視圖,且不受數(shù)據(jù)源的束縛。例如,layout可以使用裝飾視圖自定義集合視圖背景。
與UITableView不同,UICollectionView不會(huì)在數(shù)據(jù)源提供的cell和supplementary view 上施加特定的樣式,只提供空白的畫布。你需要為其構(gòu)建視圖層次結(jié)構(gòu)、顯示圖像,也可以動(dòng)態(tài)繪制內(nèi)容。
UICollectionView的數(shù)據(jù)源對(duì)象負(fù)責(zé)提供cell和supplementary view,但dataSource從來不會(huì)直接創(chuàng)建cell、supplementary view。當(dāng)需要展示新的視圖時(shí),數(shù)據(jù)源對(duì)象使用集合視圖的dequeueReusableCellWithReuseIdentifier: forIndexPath:或dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:方法出列所需類型的視圖。如果隊(duì)列存在所需類型的視圖,則會(huì)直接出列所需視圖;如果隊(duì)列沒有所需視圖,則會(huì)利用提供的nib文件、storyboard或代碼創(chuàng)建。
現(xiàn)在,添加UICollectionReusableView類,在重用視圖上添加UILabel用以顯示header、footer相關(guān)內(nèi)容。
創(chuàng)建一個(gè)新的文件,選擇iOS > Source > Cocoa Touch Class模板,點(diǎn)擊Next;Class內(nèi)容為CollectionReusableView,Subclass of一欄選擇UICollectionReusableView,點(diǎn)擊Next;選擇文件位置,點(diǎn)擊Create創(chuàng)建文件。
進(jìn)入CollectionReusableView.h,聲明一個(gè)label屬性。
@interface CollectionReusableView : UICollectionReusableView
@property (strong, nonatomic) UILabel *label;
@end
進(jìn)入CollectionReusableView.m,在實(shí)現(xiàn)部分初始化UILabel對(duì)象:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// 初始化label,設(shè)置文字顏色,最后添加label到重用視圖。
_label = [[UILabel alloc] initWithFrame:CGRectMake(20, 0, self.bounds.size.width-40, self.bounds.size.height)];
_label.textColor = [UIColor blackColor];
[self addSubview:_label];
}
return self;
}
4.數(shù)據(jù)源方法
UICollectionView必須有數(shù)據(jù)源data source,數(shù)據(jù)源對(duì)象為UICollectionView提供展示的內(nèi)容。數(shù)據(jù)源對(duì)象可能來自于app的data model,也可能來自管理UICollectionView的視圖控制器。數(shù)據(jù)源對(duì)象必須遵守UICollectionViewDataSource協(xié)議,并為UICollectionView提供以下內(nèi)容:
- 通過實(shí)現(xiàn)
numberOfSectionsInCollectionView:方法獲取集合視圖包含的section數(shù)量。如果沒有實(shí)現(xiàn)該方法,section數(shù)量默認(rèn)為1。 - 通過實(shí)現(xiàn)
collectionView: numberOfItemsInSection:方法獲取指定section所包含的item數(shù)量。 - 通過實(shí)現(xiàn)
collectonView: cellForItemAtIndexPath:方法返回指定item所使用的視圖類型。
Section和item是UICollectionView基本組織結(jié)構(gòu)。UICollectionView至少包含一個(gè)section,每個(gè)section包含零至多個(gè)item。Item用來顯示主要內(nèi)容,section將這些item分組顯示。
要實(shí)現(xiàn)UICollectionViewDataSource數(shù)據(jù)源方法,必須遵守UICollectionViewDataSource協(xié)議。在ViewController.m的interface聲明遵守UICollectionViewDataSource協(xié)議:
@interface ViewController ()<UICollectionViewDataSource>
將數(shù)據(jù)源委托給當(dāng)前控制器,需要將collectionView初始化方法中的_collectionView.dataSource = self代碼取消注釋。
下面實(shí)現(xiàn)UICollectionViewDataSource協(xié)議方法:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 2;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 6;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
// randomColor為UIColor類擴(kuò)展方法。
cell.backgroundColor = [UIColor randomColor];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
CollectionReusableView *reusableView;
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
// 設(shè)置header內(nèi)容。
reusableView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:headerIdentifier forIndexPath:indexPath];
reusableView.label.textAlignment = NSTextAlignmentCenter;
reusableView.label.text = [NSString stringWithFormat:@"Section %li",indexPath.section];
} else {
// 設(shè)置footer內(nèi)容。
reusableView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:footerIdentifier forIndexPath:indexPath];
reusableView.label.textAlignment = NSTextAlignmentNatural;
reusableView.label.text = [NSString stringWithFormat:@"Section %li have %li items",indexPath.section,[collectionView numberOfItemsInSection:indexPath.section]];
}
return reusableView;
}
NSTextAlignmentNatural會(huì)使用app當(dāng)前本地化方式對(duì)齊文本。如果默認(rèn)從左到右對(duì)齊,則為NSTextAlignmentLeft;如果默認(rèn)從右到左對(duì)齊,則為NSTextAlignmentRight。
通過上面代碼可以看到,collectionView有兩個(gè)section,每個(gè)section有6個(gè)item。randomColor為UIColor分類擴(kuò)展方法。
現(xiàn)在添加UIColor擴(kuò)展文件,點(diǎn)擊File > New > File...,選擇iOS > Source > Objective-C File模板,點(diǎn)擊Next;在File名稱一欄填寫RandomColor,File Type選取Category,Class選取UIColor,點(diǎn)擊Next;選擇文件位置,點(diǎn)擊Create創(chuàng)建文件。
進(jìn)入UIColor+RandomColor.h方法,添加以下類方法:
@interface UIColor (RandomColor)
+ (UIColor *)randomColor;
@end
進(jìn)入UIColor+RandomColor.m,在實(shí)現(xiàn)部分添加以下代碼:
+ (UIColor *)randomColor {
CGFloat red = arc4random_uniform(255)/255.0;
CGFloat green = arc4random_uniform(255)/255.0;
CGFloat blue = arc4random_uniform(255)/255.0;
return [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
}
在調(diào)用dequeueReusableCellWithReuseIdentifier: forIndexPath:方法前,必須使用registerClass: forCellWithReuseIdentifier:或registerNib: forCellWithIdentifier:方法告知集合視圖如何創(chuàng)建指定類型cell。當(dāng)重用隊(duì)列中沒有指定類型cell時(shí),collection view會(huì)使用上述注冊(cè)方法自動(dòng)創(chuàng)建cell。如果你想要取消注冊(cè),可以將class指定為nil。注冊(cè)時(shí)的標(biāo)志符不能為nil和空字符串。
注冊(cè)supplementary view時(shí),還需要額外指定一個(gè)稱為類型字符串kind string的附加標(biāo)志符。layout負(fù)責(zé)定義各自支持的補(bǔ)充視圖種類。例如,UICollectionViewFlowLayout支持兩種補(bǔ)充視圖:section header、section footer。為了識(shí)別這兩種類型視圖,flow layout定義了UICollectionElementKindSectionHeader和UICollectionElementKindSectionFooter字符串常量。在布局時(shí),集合視圖將包括類型字符串和其它布局屬性的layout發(fā)送給數(shù)據(jù)源,數(shù)據(jù)源使用類型字符串kind string和重用標(biāo)志符reuse identifier決定出列視圖。
注冊(cè)是一次性操作,且必須在嘗試出列cell、supplementary view前注冊(cè)。注冊(cè)之后,可以根據(jù)需要出列任意次數(shù)cell、supplementary view,無需再次注冊(cè)。不建議出列一個(gè)或多個(gè)視圖后更改注冊(cè)信息,最好一次注冊(cè),始終使用。
下面注冊(cè)cell、header、footer:
static NSString * const cellIdentifier = @"cellIdentifier";
static NSString * const headerIdentifier = @"headerIdentifier";
static NSString * const footerIdentifier = @"footerIdentifier";
@implementation ViewController
- (void)viewDidLoad {
...
// 注冊(cè)cell、headerView。
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:cellIdentifier];
[self.collectionView registerClass:[CollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:headerIdentifier];
[self.collectionView registerClass:[CollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:footerIdentifier];
}
現(xiàn)在運(yùn)行demo,顯示如下:

雖然是網(wǎng)格布局,但cell大小、間距均需修改,且沒有顯示section header、section footer,這些內(nèi)容由UICollectionViewDelegateFlowLayout協(xié)議定義。
5.使用Flow Layout
UICollectionViewDelegate是一個(gè)可選但推薦實(shí)現(xiàn)的協(xié)議,用于管理與內(nèi)容呈現(xiàn)、交互相關(guān)的問題。其主要工作是管理cell的高亮、選中,但可以為其擴(kuò)展其它功能。例如,流布局UICollectionViewDelegateFlowLayout協(xié)議增加了控制cell大小、間距功能。
Flow Layout實(shí)現(xiàn)了基于行的中斷布局,即layout將cell放置在線性路徑上,并盡可能多的沿著該路徑排布cell。如果當(dāng)前路徑空間不足,layout將創(chuàng)建一個(gè)新路徑并繼續(xù)布局。下圖顯示了垂直滾動(dòng)的流布局。在這種情況下,cell橫向放置,新增加的路徑位于之前路徑下方。Section可以選擇性的添加section header、section
footer視圖。

Flow Layout除了實(shí)現(xiàn)網(wǎng)格布局,還可以實(shí)現(xiàn)許多不同設(shè)計(jì)。例如:通過調(diào)整cell間距minimumInteritemSpacing、大小itemSize來創(chuàng)建在滾動(dòng)方向只有一個(gè)cell的布局。cell大小也可以不同,這樣會(huì)產(chǎn)生比傳統(tǒng)網(wǎng)格更不對(duì)稱的布局。
可以通過Xcode中的Interface Builder,或純代碼配置flow layout。步驟如下:
- 創(chuàng)建flow layout,并將其分配給
UICollectionView。 - 配置cell大小
itemSize。如果沒有設(shè)置,默認(rèn)寬高均為50。 - 配置cell行
minimumLineSpacing、cell間minimumInteritemSpacing間距,默認(rèn)值為10.0。 - 如果用到了section header、section footer,配置其大小
headerReferenceSize、footerReferenceSize。默認(rèn)值為(0,0)。 - 指定
layout滑動(dòng)方向scrollDirection。默認(rèn)滑動(dòng)方向?yàn)?code>UICollectionViewScrollDirectionVertical。
UICollectionView所使用的layout與應(yīng)用程序視圖層級(jí)結(jié)構(gòu)中使用的自動(dòng)布局Auto Layout不同,不要混淆集合視圖內(nèi)layout對(duì)象與父視圖內(nèi)重新定位子視圖的layoutSubviews。layout對(duì)象從不直接觸及其管理的視圖,因?yàn)閷?shí)質(zhì)上layout并不擁有任何視圖。相反,layout只生成集合視圖中cell、supplementary view、decoration view的位置、大小和可視外觀屬性,并將這些屬性提供給UICollectionView,由UICollectionView將這些屬性應(yīng)用于實(shí)際視圖對(duì)象。
聲明ViewController遵守UICollectionViewDelegate、UICollectionViewDelegateFlowLayout協(xié)議。將delegate賦給當(dāng)前控制器,即取消collectionView初始化方法中_collectionView.delegate = self;的注釋。
5.1設(shè)置cell大小itemSize
所有cell大小一致,最為快捷方式是為itemSize屬性賦值,如果cell大小不同,則必須使用collectionView: layout: sizeForItemAtIndexPath:方法。

如果cell大小不同,則每行cell數(shù)量可能不同。
進(jìn)入ViewController.m,在實(shí)現(xiàn)部分添加以下代碼,配置cell大小。
// 設(shè)置item大小。
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(153, 128);
}
運(yùn)行demo,如下所示:

5.2設(shè)置section header和section footer大小
在布局section header、section footer時(shí),只有與滑動(dòng)方向相同的值會(huì)被采用。例如,垂直滾動(dòng)的UICollectionView,layout只使用colllectionView: layout: referenceSizeForHeaderInSection:、collectionView: layout: referenceSizeForFooterInSection:、headerReferenceSize、footerReferenceSize提供的高,寬會(huì)被設(shè)置為UICollectionView的寬。如果滑動(dòng)方向的長(zhǎng)度被設(shè)置為0,則supplementary view不可見。
進(jìn)入ViewController.m,在實(shí)現(xiàn)部分添加以下代碼,設(shè)置section header、section footer大小。
// 設(shè)置section header大小。
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return section == 0 ? CGSizeMake(40, 40) : CGSizeMake(45, 45);
}
// 設(shè)置section footer大小。
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
return CGSizeMake(35, 35);
}
運(yùn)行demo,如下所示:

5.3設(shè)置item間距minimumInteritemSpacing
利用flow layout可以指定cell間、行間最小間距,但其實(shí)際間距可能大于最小間距。當(dāng)布局時(shí),flow layout將cell添加到當(dāng)前行,直到?jīng)]有足夠的空間來放置另一個(gè)cell。如果剛好可以排布整數(shù)個(gè)cell,那么cell間的間距等于最小間距。如果行尾有額外的空間,又不能放下另一個(gè)cell,flow layout將增加cell間距,直到cell在行內(nèi)均勻排布,這時(shí)cell間距將大于minimumInteritemSpacing。

進(jìn)入ViewController.m,在實(shí)現(xiàn)部分添加以下代碼,設(shè)置item間距。
// 設(shè)置item間距。
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 20;
}
運(yùn)行demo,如下所示:

這里每行只能排布兩個(gè)cell,所以實(shí)際間距大于設(shè)置的最小間距20。
5.4設(shè)置行間距minimumLineSpacing
對(duì)于行間距,flow layout采用與設(shè)置cell間距一樣技術(shù)。如果所有cell大小相同,flow layout會(huì)嚴(yán)格遵守最小間距設(shè)置,即每一行的cell在同一條線上,相鄰行cell間距等于minimumLineSpacing。
如果cell大小不同,flow layout會(huì)在滑動(dòng)方向選取每行最大cell。例如,在垂直方向滑動(dòng),flow layout會(huì)選取每行高最大的cell,隨后設(shè)置這些高最大的cell間距為minimumLineSpacing。如果這些高最大的cell位于行不同位置,行間距看起來會(huì)大于minimumLineSpacing。如下所示:

進(jìn)入ViewController.m,在實(shí)現(xiàn)部分添加以下代碼,設(shè)置item行間距。
// 設(shè)置行間距。
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 20;
}
運(yùn)行demo,如下所示:

這個(gè)demo中所有cell大小相同,所以這里的minimumLineSpacing會(huì)嚴(yán)格遵守設(shè)置的minimumLineSpacing間距20。
5.5使用section inset設(shè)置內(nèi)容邊距
使用sectionInset可以調(diào)整可供放置cell區(qū)域大小,如增加section header、section footer與cell間距,增加行首、行尾間距。下圖顯示了sectionInset如何影響垂直滾動(dòng)的UICollectionView。

因?yàn)?code>sectionInset減少了可供放置cell的空間,可以用此屬性限制每行cell數(shù)量。例如,在非滑動(dòng)方向設(shè)置inset,可以減少每行可用空間,同時(shí)配合設(shè)置itemSize,可以控制每行cell數(shù)量。
繼續(xù)在ViewController.m實(shí)現(xiàn)部分添加以下代碼,設(shè)置sectionInset。
// 設(shè)置頁(yè)邊距。
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 20, 0, 20);
}
運(yùn)行demo,如下所示:

使用UICollectionViewDelegateFlowLayout協(xié)議可以動(dòng)態(tài)調(diào)整布局信息。例如,不同item大小不同,不同section內(nèi)item間距不同。如果沒有提供代理方法,flow layout會(huì)使用通過屬性設(shè)置的值。上面代碼除設(shè)置section header大小部分,均可使用屬性進(jìn)行設(shè)值,如下所示:
- (UICollectionViewFlowLayout *)flowLayout {
if (!_flowLayout) {
...
// 通過屬性設(shè)值。
_flowLayout.itemSize = CGSizeMake(153, 128);
_flowLayout.footerReferenceSize = CGSizeMake(35, 35);
_flowLayout.minimumLineSpacing = 20;
_flowLayout.minimumInteritemSpacing = 20;
_flowLayout.sectionInset = UIEdgeInsetsMake(0, 20, 0, 20);
}
return _flowLayout;
}
現(xiàn)在運(yùn)行app,如下所示:

6.數(shù)據(jù)模型
高性能的數(shù)據(jù)源使用section和item來組織其底層數(shù)據(jù)對(duì)象,這樣會(huì)使數(shù)據(jù)源方法更易實(shí)現(xiàn)。數(shù)據(jù)源方法會(huì)被頻繁調(diào)用,所以在數(shù)據(jù)源檢索數(shù)據(jù)時(shí)必須足夠快。
一個(gè)簡(jiǎn)單的解決辦法(但不是唯一的)是讓數(shù)據(jù)模型使用一組嵌套數(shù)組,嵌套數(shù)組內(nèi)元素為section的數(shù)組,section數(shù)組內(nèi)元素為該section內(nèi)item。檢索某個(gè)item就變成了先檢索其section數(shù)組,再在該section數(shù)組內(nèi)檢索該item。這種模式適合于中等規(guī)模的數(shù)據(jù)模型。

當(dāng)設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)時(shí),始終可以從簡(jiǎn)單數(shù)組開始,根據(jù)需要遷移到更高效結(jié)構(gòu)。通常,數(shù)據(jù)對(duì)象不應(yīng)成為性能瓶頸。UICollectionView通過訪問數(shù)據(jù)對(duì)象以獲得共有多少個(gè)對(duì)象,并獲取當(dāng)前屏幕上顯示對(duì)象的視圖。如果layout僅依賴于數(shù)據(jù)對(duì)象,當(dāng)數(shù)據(jù)對(duì)象包含數(shù)千個(gè)對(duì)象時(shí),性能會(huì)受到嚴(yán)重影響。
現(xiàn)在,為這個(gè)demo添加一個(gè)數(shù)據(jù)模型。
打開Xcode,選擇File > New > File...,在彈出窗口選擇iOS > Source > Cocoa Touch Class模板,點(diǎn)擊Next;Class一欄填寫SimpleModel,Subclass of選擇NSObject,點(diǎn)擊Next;選擇文件位置,點(diǎn)擊Create創(chuàng)建文件。
進(jìn)入SimpleModel.h文件,聲明一個(gè)可變數(shù)組model。
@interface SimpleModel : NSObject
@property (strong, nonatomic) NSMutableArray *model;
@end
進(jìn)入SimpleModel.m文件,設(shè)置model可變數(shù)組包含另外兩個(gè)可變數(shù)組section1、section2,這兩個(gè)可變數(shù)組分別包含六個(gè)元素。
- (instancetype)init {
self = [super init];
if (self) {
NSMutableArray *section1 = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"6", nil];
NSMutableArray *section2 = [NSMutableArray arrayWithObjects:@"A",@"B",@"C",@"D",@"E",@"F", nil];
_model = [NSMutableArray arrayWithObjects:section1,section2, nil];
}
return self;
}
打開Assets.xcassets,添加github/pro648/BasicDemos-iOS這里的照片,也可以通過文章底部的源碼鏈接下載源碼獲取。
7.自定義UICollectionViewCell子類
自定義UICollectionViewCell子類,并為其添加UIImageView和UILabel對(duì)象的屬性。
打開Xcode,選擇File > New > File...,在彈出窗口選擇iOS > Source > Cocoa Touch Class,點(diǎn)擊Next;Class一欄填寫CollectionViewCell,Subclass of選擇UICollectionViewCell,點(diǎn)擊Next;選擇文件位置,點(diǎn)擊Create創(chuàng)建文件。
進(jìn)入CollectionViewCell.h文件,聲明一個(gè)imageView和一個(gè)label屬性。
@interface CollectionViewCell : UICollectionViewCell
@property (strong, nonatomic) UIImageView *imageView;
@property (strong, nonatomic) UILabel *label;
@end
進(jìn)入CollectionViewCell.m文件,初始化imageView和label屬性。
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// 1.初始化imageView、label。
CGFloat cellWidth = self.bounds.size.width;
CGFloat cellHeight = self.bounds.size.height;
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, cellWidth, cellHeight * 4/5)];
_label = [[UILabel alloc] initWithFrame:CGRectMake(0, cellHeight * 4/5, cellWidth, cellHeight * 1/5)];
_label.textAlignment = NSTextAlignmentCenter;
// 2.添加imageView、label到cell。
[self.contentView addSubview:_imageView];
[self.contentView addSubview:_label];
}
return self;
}
進(jìn)入ViewController.m文件,導(dǎo)入CollectionViewCell.h和SimpleModel.h文件,聲明類型為SimpleModel的simpleModel屬性。
#import "CollectionViewCell.h"
#import "SimpleModel.h"
@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
...
@property (strong, nonatomic) SimpleModel *simpleModel;
@end
更新cell注冊(cè)方法,并初始化simpleModel屬性。
- (void)viewDidLoad {
...
// 更新cell注冊(cè)方法。
[self.collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:cellIdentifier];
...
// 初始化simpleModel
self.simpleModel = [[SimpleModel alloc] init];
}
現(xiàn)在更新數(shù)據(jù)源方法。
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.simpleModel.model.count;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.simpleModel.model[section] count];
}
- (CollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
// 設(shè)置imageView圖片,label文字。
NSString *imageName = [self.simpleModel.model[indexPath.section] objectAtIndex:indexPath.item];
cell.imageView.image = [UIImage imageNamed:imageName];
NSString *labelText = [NSString stringWithFormat:@"(%li, %li)",indexPath.section, indexPath.item];
cell.label.text = labelText;
return cell;
}
dataSource必須返回一個(gè)有效的視圖,不能為nil,即使由于某種原因該視圖不該被顯示。layout期望返回有效視圖,如果返回nil視圖會(huì)導(dǎo)致app終止。
運(yùn)行app,如下所示:

8.重新排序cell
自iOS 9,Collection View允許根據(jù)用戶手勢(shì)重新排序cell。如需支持重新排序功能,需要添加手勢(shì)識(shí)別器跟蹤用戶手勢(shì)與集合視圖的交互,同時(shí)更新數(shù)據(jù)源中item位置。
為UICollectionView添加長(zhǎng)按手勢(shì)識(shí)別器,并實(shí)現(xiàn)響應(yīng)方法。
- (void)viewDidLoad {
...
// 為collectionView添加長(zhǎng)按手勢(shì)。
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(reorderCollectionView:)];
[self.collectionView addGestureRecognizer:longPressGesture];
}
// 長(zhǎng)按手勢(shì)響應(yīng)方法。
- (void)reorderCollectionView:(UILongPressGestureRecognizer *)longPressGesture {
switch (longPressGesture.state) {
case UIGestureRecognizerStateBegan:{
// 手勢(shì)開始。
CGPoint touchPoint = [longPressGesture locationInView:self.collectionView];
NSIndexPath *selectedIndexPath = [self.collectionView indexPathForItemAtPoint:touchPoint];
if (selectedIndexPath) {
[self.collectionView beginInteractiveMovementForItemAtIndexPath:selectedIndexPath];
}
break;
}
case UIGestureRecognizerStateChanged:{
// 手勢(shì)變化。
CGPoint touchPoint = [longPressGesture locationInView:self.collectionView];
[self.collectionView updateInteractiveMovementTargetPosition:touchPoint];
break;
}
case UIGestureRecognizerStateEnded:{
// 手勢(shì)結(jié)束。
[self.collectionView endInteractiveMovement];
break;
}
default:{
[self.collectionView cancelInteractiveMovement];
break;
}
}
}
長(zhǎng)按手勢(shì)響應(yīng)步驟如下:
- 要開始交互式移動(dòng)item,Collection View調(diào)用
beginInteractiveMovementForItemAtIndexPath:方法; - 當(dāng)手勢(shì)識(shí)別器跟蹤到手勢(shì)變化時(shí),集合視圖調(diào)用
updateInteractiveMovementTargetPosition:方法報(bào)告最新觸摸位置; - 當(dāng)手勢(shì)結(jié)束時(shí),
UICollectionView調(diào)用endInteractiveMovement方法結(jié)束交互并更新視圖; - 當(dāng)手勢(shì)中途取消或識(shí)別失敗,
UICollectionView調(diào)用cancelInteractiveMovement方法結(jié)束交互。
如果想要對(duì)手勢(shì)識(shí)別器進(jìn)行更全面了解,可以查看手勢(shì)控制:點(diǎn)擊、滑動(dòng)、平移、捏合、旋轉(zhuǎn)、長(zhǎng)按、輕掃這篇文章。
在交互過程中,Collection view會(huì)動(dòng)態(tài)的使布局無效,以反映當(dāng)前item最新布局。默認(rèn)的layout會(huì)自動(dòng)重新排布item,你也可以自定義布局動(dòng)畫。
UICollectionViewController默認(rèn)安裝了長(zhǎng)按手勢(shì)識(shí)別器,用來重新排布集合視圖中cell,如果需要禁用重新排布cell手勢(shì),設(shè)置installStandardGestureForInteractiveMovement屬性為NO。
當(dāng)交互手勢(shì)結(jié)束時(shí),如果item位置放生了變化,UICollectionView會(huì)調(diào)用以下方法更新數(shù)據(jù)源。
// 是否允許移動(dòng)item。
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
// 更新數(shù)據(jù)源。
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
NSString *sourceObject = [self.simpleModel.model[sourceIndexPath.section] objectAtIndex:sourceIndexPath.item];
[self.simpleModel.model[sourceIndexPath.section] removeObjectAtIndex:sourceIndexPath.item];
[self.simpleModel.model[destinationIndexPath.section] insertObject:sourceObject atIndex:destinationIndexPath.item];
// 重新加載當(dāng)前顯示的item。
[collectionView reloadItemsAtIndexPaths:[collectionView indexPathsForVisibleItems]];
}
集合視圖會(huì)先調(diào)用collectionView: canMoveItemAtIndexPath:方法,看當(dāng)前item是否允許移動(dòng)。如果沒有實(shí)現(xiàn)該方法,但實(shí)現(xiàn)了collectionView: moveItemAtIndexPath: toIndexPath:方法,集合視圖會(huì)允許所有item被移動(dòng)。當(dāng)交互手勢(shì)結(jié)束時(shí),UICollectionView會(huì)自動(dòng)調(diào)用collectionView: moveItemAtIndexPath: toIndexPath:,如果該方法沒有實(shí)現(xiàn),則移動(dòng)cell請(qǐng)求會(huì)被忽略。
運(yùn)行app,移動(dòng)item。

在更新數(shù)據(jù)源時(shí),按照以下步驟操作:
- 更新數(shù)據(jù)源中數(shù)據(jù)。
- 調(diào)用
UICollectionView方法進(jìn)行插入、刪除、移動(dòng)section或item操作。
必須先更新數(shù)據(jù)源,后更改UICollectionView。UICollectionView中方法會(huì)假定當(dāng)前數(shù)據(jù)源包含正確數(shù)據(jù),如果數(shù)據(jù)有誤,集合視圖可能會(huì)得到錯(cuò)誤數(shù)據(jù),也可能請(qǐng)求不存在的數(shù)據(jù),導(dǎo)致app崩潰。
以編程的方式添加、刪除、移動(dòng)單個(gè)item時(shí),collection view會(huì)自動(dòng)創(chuàng)建動(dòng)畫以反映更改。如果你想要將多個(gè)插入、刪除、移動(dòng)操作合并為一個(gè)動(dòng)畫,則必須將這些操作放到一個(gè)塊內(nèi),并將該塊傳遞給performBatchUpdates: completion:方法。批量更新會(huì)在同一時(shí)間更新所有操作。
在
performBatchUpdates: completion:方法中,刪除操作會(huì)在插入操作之前進(jìn)行。也就是說,刪除操作的index是collection view在執(zhí)行批量更新batch update前的index,插入操作的index是collection view在執(zhí)行完批量更新中刪除操作后的index。
9.使用drag and drop排序
iOS 11增加了系統(tǒng)范圍的拖放操作drag and drop,讓用戶可以快速簡(jiǎn)單的將文本、圖像和文件從一個(gè)app移動(dòng)到另一個(gè)app,只需輕點(diǎn)并按住即可提取其內(nèi)容,拖放到其它位置。
UICollectionView通過專用API支持drag和drop,我們可以使用drag和drop來重新排序cell。
- 為了支持drag操作,定義一個(gè)drag delegate對(duì)象,并將其賦值給collection view的
dragDelegate,該對(duì)象必須遵守UICollectionViewDragDelegate協(xié)議; - 為了支持drop操作,定義一個(gè)drop delegate對(duì)象,并將其賦值給collection view的
dropDelegate,該對(duì)象必須遵守UICollectionViewDropDelegate協(xié)議。
注釋掉上一部分使用長(zhǎng)按手勢(shì)重新排序cell的代碼,現(xiàn)在使用drag and drop重新排序。
所有拖放drag and drop功能都可以在iPad上使用。在iPhone上,拖放功能只能在應(yīng)用內(nèi)使用,不可在應(yīng)用間拖放。
app可以只遵守
UICollectionViewDragDelegate、UICollectionViewDropDelegate中的一個(gè)協(xié)議。
進(jìn)入ViewController.m文件,聲明視圖控制器遵守UICollectionViewDragDelegate、UICollectionViewDropDelegate協(xié)議。同時(shí),將視圖控制器賦值給dragDelegate、dropDelegate屬性。
@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDragDelegate, UICollectionViewDropDelegate>
- (void)viewDidLoad {
...
// 開啟拖放手勢(shì),設(shè)置代理。
self.collectionView.dragInteractionEnabled = YES;
self.collectionView.dragDelegate = self;
self.collectionView.dropDelegate = self;
}
9.1從集合視圖中拖起item
UICollectionView管理大部分與拖動(dòng)相關(guān)的交互,但你需要指定要拖動(dòng)的item。當(dāng)拖動(dòng)手勢(shì)發(fā)生時(shí),集合視圖創(chuàng)建一個(gè)拖動(dòng)會(huì)話,調(diào)用collectionView:itemsForBeginningDragSession:atIndexPath:代理方法。如果該方法返回非空數(shù)組,則集合視圖將開始拖動(dòng)指定item。如果不允許拖動(dòng)指定索引路徑的item,則返回空數(shù)組。
在實(shí)現(xiàn)collectionView:itemsForBeginningDragSession:atIndexPath:方法時(shí),按照以下步驟操作:
- 創(chuàng)建一個(gè)或多個(gè)
NSItemProvider,使用NSItemProvider傳遞集合視圖item內(nèi)容。 - 將每個(gè)
NSItemProvider封裝在對(duì)應(yīng)UIDragItem對(duì)象中。 - 考慮為每個(gè)
dragItem的localObject分配要傳遞的數(shù)據(jù)。這一步驟是可選的,但在同一app內(nèi)拖放時(shí),localObject可以加快數(shù)據(jù)傳遞。 - 返回
dragItem。
在ViewController.m文件中,實(shí)現(xiàn)上述方法:
- (NSArray <UIDragItem *>*)collectionView:(UICollectionView *)collectionView itemsForBeginningDragSession:(id<UIDragSession>)session atIndexPath:(NSIndexPath *)indexPath {
NSString *imageName = [self.simpleModel.model[indexPath.section] objectAtIndex:indexPath.item];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithObject:imageName];
UIDragItem *dragItem = [[UIDragItem alloc] initWithItemProvider:itemProvider];
dragItem.localObject = imageName;
return @[dragItem];
}
如果需要支持一次拖動(dòng)多個(gè)item,還需要實(shí)現(xiàn)collectionView:itemsForAddingToDragSession:atIndexPath:point:方法,其實(shí)現(xiàn)代碼與上面部分相同。
運(yùn)行app,如下所示:

使用collectionView:dragPreviewParametersForItemAtIndexPath:方法,可以自定義拖動(dòng)過程中cell外觀。如果沒有實(shí)現(xiàn)該方法,或?qū)崿F(xiàn)后返回nil,collection view將使用cell原樣式呈現(xiàn)。
在該方法的實(shí)現(xiàn)部分,創(chuàng)建一個(gè)UIDragPreviewParameters對(duì)象,并更新指定item的預(yù)覽信息。使用UIDragPreviewParameters可以指定cell的可視部分,或改變cell背景顏色,如下所示:
// 設(shè)置拖動(dòng)預(yù)覽信息。
- (nullable UIDragPreviewParameters *)collectionView:(UICollectionView *)collectionView dragPreviewParametersForItemAtIndexPath:(NSIndexPath *)indexPath {
// 預(yù)覽圖為圓角,背景色為clearColor。
UIDragPreviewParameters *previewParameters = [[UIDragPreviewParameters alloc] init];
CollectionViewCell *cell = (CollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
previewParameters.visiblePath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:10];
previewParameters.backgroundColor = [UIColor clearColor];
return previewParameters;
}
運(yùn)行app,如下所示:

可以看到,預(yù)覽cell為圓角。
9.2 接收拖動(dòng)cell內(nèi)容
當(dāng)內(nèi)容被拖入集合視圖邊界內(nèi)時(shí),集合視圖會(huì)調(diào)用collectonView:canHandleDropSession:方法,查看當(dāng)前數(shù)據(jù)模型是否可以接收拖動(dòng)的內(nèi)容。如果可以接收拖動(dòng)的內(nèi)容,集合視圖會(huì)繼續(xù)調(diào)用其它方法。
當(dāng)用戶手指移動(dòng)時(shí),集合視圖跟蹤手勢(shì),檢測(cè)可能的drop位置,并通知collectionView:dropSessionDidUpdate:withDestinationIndexPath:代理方法。該方法可選實(shí)現(xiàn),但一般推薦實(shí)現(xiàn)。實(shí)現(xiàn)該方法后,UICollectonView會(huì)及時(shí)反饋將如何合并、放置拖動(dòng)的cell到當(dāng)前視圖。該方法會(huì)被頻繁調(diào)用,實(shí)現(xiàn)過程要盡可能快速、簡(jiǎn)單。
當(dāng)手指離開屏幕時(shí),UICollectionView會(huì)調(diào)用collectionView:performDropWithCoordinator:方法,必須實(shí)現(xiàn)該方法以接收拖動(dòng)的數(shù)據(jù)。實(shí)現(xiàn)步驟如下:
枚舉coordinator的
items屬性。-
不同類型item,采取不同接收方法:
- 如果item的
sourceIndexPath存在,則item始于集合視圖,可以使用批量更新batch update從當(dāng)前位置刪除item,插入到新的位置。 - 如果item的
localObject屬性存在,則item始于app其它位置,必須插入item到數(shù)據(jù)模型。 - 前面兩種均不滿足時(shí),使用
NSItemProvider的itemProvider屬性,異步提取數(shù)據(jù),插入到數(shù)據(jù)模型。
- 如果item的
更新數(shù)據(jù)模型,刪除、插入collection view中item。
繼續(xù)在ViewController.m中添加以下代碼:
// 是否接收拖動(dòng)的item。
- (BOOL)collectionView:(UICollectionView *)collectionView canHandleDropSession:(id<UIDropSession>)session {
return [session canLoadObjectsOfClass:[NSString class]];
}
// 拖動(dòng)過程中不斷反饋item位置。
- (UICollectionViewDropProposal *)collectionView:(UICollectionView *)collectionView dropSessionDidUpdate:(id<UIDropSession>)session withDestinationIndexPath:(NSIndexPath *)destinationIndexPath {
UICollectionViewDropProposal *dropProposal;
if (session.localDragSession) {
// 拖動(dòng)手勢(shì)源自同一app。
dropProposal = [[UICollectionViewDropProposal alloc] initWithDropOperation:UIDropOperationMove intent:UICollectionViewDropIntentInsertAtDestinationIndexPath];
} else {
// 拖動(dòng)手勢(shì)源自其它app。
dropProposal = [[UICollectionViewDropProposal alloc] initWithDropOperation:UIDropOperationCopy intent:UICollectionViewDropIntentInsertAtDestinationIndexPath];
}
return dropProposal;
}
- (void)collectionView:(UICollectionView *)collectionView performDropWithCoordinator:(id<UICollectionViewDropCoordinator>)coordinator {
// 如果coordinator.destinationIndexPath存在,直接返回;如果不存在,則返回(0,0)位置。
NSIndexPath *destinationIndexPath = coordinator.destinationIndexPath ? coordinator.destinationIndexPath : [NSIndexPath indexPathForItem:0 inSection:0];
// 在collectionView內(nèi),重新排序時(shí)只能拖動(dòng)一個(gè)cell。
if (coordinator.items.count == 1 && coordinator.items.firstObject.sourceIndexPath) {
NSIndexPath *sourceIndexPath = coordinator.items.firstObject.sourceIndexPath;
// 將多個(gè)操作合并為一個(gè)動(dòng)畫。
[collectionView performBatchUpdates:^{
// 將拖動(dòng)內(nèi)容從數(shù)據(jù)源刪除,插入到新的位置。
NSString *imageName = coordinator.items.firstObject.dragItem.localObject;
[self.simpleModel.model[sourceIndexPath.section] removeObjectAtIndex:sourceIndexPath.item];
[self.simpleModel.model[destinationIndexPath.section] insertObject:imageName atIndex:destinationIndexPath.item];
// 更新collectionView。
[collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
} completion:nil];
// 以動(dòng)畫形式移動(dòng)cell。
[coordinator dropItem:coordinator.items.firstObject.dragItem toItemAtIndexPath:destinationIndexPath];
}
}
現(xiàn)在運(yùn)行app,如下所示:

對(duì)于必須使用NSItemProvider檢索的數(shù)據(jù),需要使用dropItem:toPlaceHolderInsertedAtIndexPath:withReuseIdentifier:cellUpdateHandler:方法先將占位符placeholder插入,之后異步檢索數(shù)據(jù),具體方法這里不再介紹。
iOS 11也為
UITableView增加了drag和drop功能,其API非常相似。
10. 總結(jié)
UICollectionView非常強(qiáng)大,除系統(tǒng)提供的這些布局風(fēng)格,你還可以使用自定義布局custom layout滿足你的各種需求。
如果覺得從數(shù)據(jù)源獲取數(shù)據(jù)很耗時(shí),可以使用UICollectionViewDataSourcePrefetching協(xié)議,該協(xié)議會(huì)協(xié)助你的數(shù)據(jù)源在還未調(diào)用collectionView:cellForItemAtIndexPath:方法時(shí)進(jìn)行預(yù)加載。詳細(xì)內(nèi)容可以查看文檔進(jìn)一步學(xué)習(xí)。
Demo名稱:CollectionView
源碼地址:https://github.com/pro648/BasicDemos-iOS
參考資料: