原文地址:http://www.reibang.com/p/db55bd5f5aeb
這篇文章是我學(xué)習(xí)了大神的文章之后赃承,幾乎抄了一份下來梭纹,所以建議通過上面地址去看原文。
首先學(xué)習(xí)幾個(gè)英文單詞
- abstract: 抽象的
- concrete: 具體的 (反義詞??)
- reusable: 可重用的
- attributes: 屬性
- collection: 集合丸凭、收集
- drag and drop: 拖和放
- supplementary: 補(bǔ)充的
- decoration: 裝飾、修飾右蕊、裝飾品
- vertical: 垂直的
- layout: 布局
- reference: 參考
UICollectionView
是我們常說的集合視圖足丢,它在iOS6中引入,是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)對象的關(guān)系:
其中data source
提供呈現(xiàn)數(shù)據(jù)的視圖對象,collection view layout
提供視圖布局信息康聂,而collection view
負(fù)責(zé)將數(shù)據(jù)和布局信息合并后呈現(xiàn)到屏幕上贰健。需要注意的是,在創(chuàng)建UICollectionView
時(shí)恬汁,必須傳遞一個(gè)UICollectionViewLayout
對象伶椿,這里的UICollectionViewLayout
是一個(gè)抽象基類abstract base class
,不能直接使用,必須使用其子類氓侧。例如脊另,在創(chuàng)建網(wǎng)格布局時(shí)一般使用UICollectionViewFlowLayout
這個(gè)具體類concrete class
。
下面表格列出了UIKit
中與集合視圖相關(guān)的類约巷,并按照各自扮演的角色進(jìn)行分類:
1. UICollectionView和UICollectionViewController
UICollectionView
派生自UIScrollView
偎痛,定義集合視圖內(nèi)容區(qū)域,將dataSource
的數(shù)據(jù)與layout
提供的布局信息合并后呈現(xiàn)到屏幕上独郎。UICollectionViewController
為集合視圖提供了控制器級(jí)別支持踩麦,UICollectionViewController
的使用是可選的。
2. UICollectionViewDataSource和UICollectionViewDelegate協(xié)議
dataSource
為集合視圖提供數(shù)據(jù)氓癌,是UICollectionView
中最重要谓谦、必須提供的對象。要實(shí)現(xiàn)dataSource
中的方法贪婉,必須創(chuàng)建一個(gè)遵守UICollectionViewDataSource
協(xié)議的對象反粥。通過UICollectionView
的delegate
對象可以監(jiān)聽集合視圖狀態(tài)、自定義視圖疲迂。例如才顿,使用delegate
跟蹤item
是否高亮、選中尤蒿。與數(shù)據(jù)源對象不同郑气,代理對象不是必須實(shí)現(xiàn)。
3. UICollectionReusableView和UICollectionViewCell
UICollectionView
中顯示的所有視圖都必須是UICollectionReusableView
類的實(shí)例优质,該類支持回收機(jī)制(循環(huán)使用視圖,而非創(chuàng)建新的視圖)军洼,以便提高性能巩螃,特別是在滑動(dòng)屏幕時(shí)。UICollectionViewCell
用來顯示主要數(shù)據(jù)匕争,也是可重用視圖避乏。
4. UICollectionViewLayout、UICollectionViewLayoutAttributes和UICollectionViewUpdateItem布局
使用UICollectionViewLayout
的子類為集合視圖內(nèi)元素提供位置甘桑、大小拍皮、視覺屬性等布局信息歹叮。在布局過程中,layout對象創(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í)例。
5. 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í)時(shí)(例如網(wǎng)絡(luò)請求)的情況下非常有用。
- iOS 11增加了系統(tǒng)范圍的施放措施drag and drop,讓用戶可以快速簡單的將文本句旱、圖像和文件從一個(gè)app移動(dòng)到另一個(gè)app阳藻。
現(xiàn)在我們就通過這篇文章,對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
為視圖控制器添加UICollectionView
,進(jìn)入ViewController.m
,在接口部分添加以下聲明:
@interface ViewController ()
@property (strong, nonatomic) UICollectionView *collectionView;
@property (strong, nonatomic) UICollectionViewFlowLayout *flowLayout;
@end
在實(shí)現(xiàn)部分初始化UICollectionViewFlowLayout
夹厌、UICollectionView
對象。
- (UICollectionViewFlowLayout *)flowLayout {
if (!_flowLayout) {
// 初始化UICollectionViewFlowLayout對象裆悄,設(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.view
- (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必須時(shí)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
可以選擇添加頁眉section header
和頁腳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ù)源對象負(fù)責(zé)提供cell
和supplementary view
居暖,但dataSource
從來不會(huì)直接創(chuàng)建cell
顽频、supplementary view
。當(dāng)需要展示新的視圖時(shí)膝但,數(shù)據(jù)源對象使用集合視圖的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對象:
- (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ù)源對象為UICollectionView
提供展示的內(nèi)容。數(shù)據(jù)源對象可能來自于app的data model恢准,也可能來自管理UICollectionView
的視圖控制器魂挂。數(shù)據(jù)源對象必須遵守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)前本地化方式對齊文本昼弟。如果默認(rèn)從左到右對齊,則為NSTextAlignmentLeft奕筐;如果默認(rèn)從右到左對齊舱痘,則為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,F(xiàn)ile 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ì)使用上述注冊方法自動(dòng)創(chuàng)建cell。如果你想要取消注冊螃征,可以將class指定為nil。注冊時(shí)的標(biāo)志符不能為nil和空字符串螃壤。
注冊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決定出列視圖。
注冊是一次性操作走诞,且必須在嘗試出列cell、supplementary view前注冊蛤高。注冊之后蚣旱,可以根據(jù)需要出列任意次數(shù)cell、supplementary view戴陡,無需再次注冊塞绿。不建議出列一個(gè)或多個(gè)視圖后更改注冊信息,最好一次注冊恤批,始終使用异吻。
下面注冊cell、header喜庞、footer:
static NSString * const cellIdentifier = @"cellIdentifier";
static NSString * const headerIdentifier = @"headerIdentifier";
static NSString * const footerIdentifier = @"footerIdentifier";
@implementation ViewController
- (void)viewDidLoad {
...
// 注冊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)格更不對稱的布局惹骂。
可以通過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
對象與父視圖內(nèi)重新定位子視圖的layoutSubviews
。layout
對象從不直接觸及其管理的視圖浸锨,因?yàn)閷?shí)質(zhì)上layout
并不擁有任何視圖唇聘。相反,layout
只生成集合視圖中cell柱搜、supplementary view迟郎、decoration view的位置、大小和可視外觀屬性聪蘸,并將這些屬性提供給UICollectionView
宪肖,由UICollectionView
將這些屬性應(yīng)用于實(shí)際視圖對象表制。
聲明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)方向的長度被設(shè)置為0赃承,則supplementary view不可見。
進(jìn)入ViewController.m
[圖片上傳中...(3151492-8159712abfe8cc7b.png-c34530-1516353881306-0)]
悴侵,在實(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
對于行間距,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è)置頁邊距秘车。
- (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ù)對象宫静,這樣會(huì)使數(shù)據(jù)源方法更易實(shí)現(xiàn)走净。數(shù)據(jù)源方法會(huì)被頻繁調(diào)用,所以在數(shù)據(jù)源檢索數(shù)據(jù)時(shí)必須足夠快囊嘉。
一個(gè)簡單的解決辦法(但不是唯一的)是讓數(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í),始終可以從簡單數(shù)組開始抛虏,根據(jù)需要遷移到更高效結(jié)構(gòu)博其。通常,數(shù)據(jù)對象不應(yīng)成為性能瓶頸迂猴。UICollectionView
通過訪問數(shù)據(jù)對象以獲得共有多少個(gè)對象慕淡,并獲取當(dāng)前屏幕上顯示對象的視圖。如果layout僅依賴于數(shù)據(jù)對象沸毁,當(dāng)數(shù)據(jù)對象包含數(shù)千個(gè)對象時(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
對象的屬性。
打開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注冊方法福侈,并初始化simpleModel
屬性酒来。
- (void)viewDidLoad {
...
// 更新cell注冊方法。
[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ù)用戶手勢重新排序cell生蚁。如需支持重新排序功能,需要添加手勢識(shí)別器跟蹤用戶手勢與集合視圖的交互戏自,同時(shí)更新數(shù)據(jù)源中item位置邦投。
為UICollectionView
添加長按手勢識(shí)別器,并實(shí)現(xiàn)響應(yīng)方法擅笔。
- (void)viewDidLoad {
...
// 為collectionView添加長按手勢志衣。
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(reorderCollectionView:)];
[self.collectionView addGestureRecognizer:longPressGesture];
}
// 長按手勢響應(yīng)方法。
- (void)reorderCollectionView:(UILongPressGestureRecognizer *)longPressGesture {
switch (longPressGesture.state) {
case UIGestureRecognizerStateBegan:{
// 手勢開始猛们。
CGPoint touchPoint = [longPressGesture locationInView:self.collectionView];
NSIndexPath *selectedIndexPath = [self.collectionView indexPathForItemAtPoint:touchPoint];
if (selectedIndexPath) {
[self.collectionView beginInteractiveMovementForItemAtIndexPath:selectedIndexPath];
}
break;
}
case UIGestureRecognizerStateChanged:{
// 手勢變化念脯。
CGPoint touchPoint = [longPressGesture locationInView:self.collectionView];
[self.collectionView updateInteractiveMovementTargetPosition:touchPoint];
break;
}
case UIGestureRecognizerStateEnded:{
// 手勢結(jié)束。
[self.collectionView endInteractiveMovement];
break;
}
default:{
[self.collectionView cancelInteractiveMovement];
break;
}
}
}
長按手勢響應(yīng)步驟如下:
- 要開始交互式移動(dòng)item弯淘,Collection View調(diào)用
beginInteractiveMovementForItemAtIndexPath:
方法绿店; - 當(dāng)手勢識(shí)別器跟蹤到手勢變化時(shí),集合視圖調(diào)用
updateInteractiveMovementTargetPosition:
方法報(bào)告最新觸摸位置耳胎; - 當(dāng)手勢結(jié)束時(shí)惯吕,
UICollectionView
調(diào)用endInteractiveMovement
方法結(jié)束交互并更新視圖惕它; - 當(dāng)手勢中途取消或識(shí)別失敗怕午,
UICollectionView
調(diào)用cancelInteractiveMovement
方法結(jié)束交互。
如果想要對手勢識(shí)別器進(jìn)行更全面了解淹魄,可以查看手勢控制:點(diǎn)擊郁惜、滑動(dòng)、平移、捏合兆蕉、旋轉(zhuǎn)羽戒、長按、輕掃這篇文章虎韵。
在交互過程中易稠,Collection view會(huì)動(dòng)態(tài)的使布局無效,以反映當(dāng)前item最新布局包蓝。默認(rèn)的layout
會(huì)自動(dòng)重新排布item驶社,你也可以自定義布局動(dòng)畫。
UICollectionViewController默認(rèn)安裝了長按手勢識(shí)別器测萎,用來重新排布集合視圖中cell亡电,如果需要禁用重新排布cell手勢,設(shè)置installStandardGestureForInteractiveMovement屬性為NO硅瞧。
當(dāng)交互手勢結(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)交互手勢結(jié)束時(shí)榆骚,UICollectionView
會(huì)自動(dòng)調(diào)用collectionView: moveItemAtIndexPath: toIndexPath:
片拍,如果該方法沒有實(shí)現(xiàn),則移動(dòng)cell請求會(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ù),也可能請求不存在的數(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,讓用戶可以快速簡單的將文本性宏、圖像和文件從一個(gè)app移動(dòng)到另一個(gè)app群井,只需輕點(diǎn)并按住即可提取其內(nèi)容,拖放到其它位置毫胜。
UICollectionView
通過專用API支持drag和drop书斜,我們可以使用drag和drop來重新排序cell。
- 為了支持drag操作酵使,定義一個(gè)drag delegate對象荐吉,并將其賦值給collection view的dragDelegate,該對象必須遵守
UICollectionViewDragDelegate
協(xié)議口渔; - 為了支持drop操作样屠,定義一個(gè)drop delegate對象,并將其賦值給collection view的dropDelegate缺脉,該對象必須遵守
UICollectionViewDropDelegate
協(xié)議痪欲。
注釋掉上一部分使用長按手勢重新排序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è)置代理。
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)手勢發(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
封裝在對應(yīng)UIDragItem
對象中院究。 - 考慮為每個(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
對象醒串,并更新指定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í)肛宋,集合視圖跟蹤手勢,檢測可能的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)過程要盡可能快速、簡單严嗜。
當(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ù)模型踢俄。
更新數(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)手勢源自同一app脆丁。
dropProposal = [[UICollectionViewDropProposal alloc] initWithDropOperation:UIDropOperationMove intent:UICollectionViewDropIntentInsertAtDestinationIndexPath];
} else {
// 拖動(dòng)手勢源自其它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 deleteItemsAtIndexPaths:@[sourceIndexPath]];
[collectionView insertItemsAtIndexPaths:@[destinationIndexPath]];
} completion:nil];
}
}
現(xiàn)在運(yùn)行app,如下所示:
對于必須使用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
參考資料: