UICollectionView學(xué)習(xí)

原文地址: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)變化等形式布局蒜鸡,以及其他任何你想像出的布局畜眨。

collectionView.png

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

collectionView.png

其中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é)議的對象反粥。通過UICollectionViewdelegate對象可以監(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ā)送給layoutprepareForCollectionViewUpdates:方法愧驱,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í)集合視圖腥泥。效果如下:

collectionView.gif

打開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ù)源提供的cellsupplementary view 上施加特定的樣式驯杜,只提供空白的畫布。你需要為其構(gòu)建視圖層次結(jié)構(gòu)做个、顯示圖像鸽心,也可以動(dòng)態(tài)繪制內(nèi)容。

UICollectionView的數(shù)據(jù)源對象負(fù)責(zé)提供cellsupplementary 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定義了UICollectionElementKindSectionHeaderUICollectionElementKindSectionFooter字符串常量。在布局時(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延都,顯示如下:

3151492-eea8b71664c499ea.png

雖然是網(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視圖括改。

3151492-5f963fa7a0dc7f26.png

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做瞪。步驟如下:

  1. 創(chuàng)建flow layout对粪,并將其分配給UICollectionView

  2. 配置cell大小itemSize装蓬。如果沒有設(shè)置著拭,默認(rèn)寬高均為50

  3. 配置cell行minimumLineSpacing牍帚、cell間minimumInteritemSpacing間距茫死,默認(rèn)值為10.0

  4. 如果用到了section header履羞、section footer峦萎,配置其大小headerReferenceSizefooterReferenceSize忆首。默認(rèn)值為(0,0)爱榔。

  5. 指定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)重新定位子視圖的layoutSubviewslayout對象從不直接觸及其管理的視圖浸锨,因?yàn)閷?shí)質(zhì)上layout并不擁有任何視圖唇聘。相反,layout只生成集合視圖中cell柱搜、supplementary view迟郎、decoration view的位置、大小和可視外觀屬性聪蘸,并將這些屬性提供給UICollectionView宪肖,由UICollectionView將這些屬性應(yīng)用于實(shí)際視圖對象表制。

聲明ViewController遵守UICollectionViewDelegateUICollectionViewDelegateFlowLayout協(xié)議控乾。將delegate賦給當(dāng)前控制器么介,即取消collectionView初始化方法中_collectionView.delegate = self;的注釋。

5.1設(shè)置cell大小itemSize

所有cell大小一致蜕衡,最為快捷方式是為itemSize屬性賦值壤短,如果cell大小不同,則必須使用collectionView: layout: sizeForItemAtIndexPath:方法慨仿。

3151492-3cde94c5045f7355.png

如果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,如下所示:


3151492-16ed352f3d81b41d.png

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浇借,如下所示:

3151492-8159712abfe8cc7b.png

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砂豌。

3151492-87e20a4f26520570.png

進(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,如下所示:

3151492-ac8f0dc2402edc49.png

這里每行只能排布兩個(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屎暇。如下所示:

3151492-8b44b5ed20d8ebe7.png

進(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抖拦,如下所示:

3151492-a04722976e6e68a0.png

這個(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。

3151492-14435588aadc6fcf.png

因?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典勇,如下所示:

3151492-94ce7bfc34524bd6.png

使用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,如下所示:

3151492-e9a59452b967b8bf.gif

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ù)模型琢蛤。

dataSource.png

當(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子類舰蟆,并為其添加UIImageViewUILabel對象的屬性。

打開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文件,初始化imageViewlabel屬性。

- (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.hSimpleModel.h文件老充,聲明類型為SimpleModelsimpleModel屬性列粪。

#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,如下所示:

simpleModel.png

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。

3151492-ddb5ce17fe04af9c.gif

在更新數(shù)據(jù)源時(shí)碉钠,按照以下步驟操作:

  1. 更新數(shù)據(jù)源中數(shù)據(jù)纲缓。

  2. 調(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文件负蠕,聲明視圖控制器遵守UICollectionViewDragDelegateUICollectionViewDropDelegate協(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í)桩蓉,按照以下步驟操作:

  1. 創(chuàng)建一個(gè)或多個(gè)NSItemProvider淋纲,使用NSItemProvider傳遞集合視圖item內(nèi)容。
  2. 將每個(gè)NSItemProvider封裝在對應(yīng)UIDragItem對象中院究。
  3. 考慮為每個(gè)dragItemlocalObject分配要傳遞的數(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产场,如下所示:

collectionViewDrag.gif

使用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,如下所示:

3151492-ebf4189fc2992d0a.gif

可以看到错蝴,預(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,如下所示:

collectionViewDrop.gif

對于必須使用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

參考資料:

  1. About iOS Collection Views

  2. Supporting Drag and Drop in Collection Views

歡迎更多指正:https://github.com/pro648/tips/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末听绳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子异赫,更是在濱河造成了極大的恐慌椅挣,老刑警劉巖头岔,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鼠证,居然都是意外死亡峡竣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門量九,熙熙樓的掌柜王于貴愁眉苦臉地迎上來适掰,“玉大人,你說我怎么就攤上這事荠列±嗬耍” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵肌似,是天一觀的道長费就。 經(jīng)常有香客問我,道長川队,這世上最難降的妖魔是什么力细? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮固额,結(jié)果婚禮上眠蚂,老公的妹妹穿的比我還像新娘。我一直安慰自己斗躏,他們只是感情好逝慧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瑟捣,像睡著了一般馋艺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迈套,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天捐祠,我揣著相機(jī)與錄音,去河邊找鬼桑李。 笑死踱蛀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贵白。 我是一名探鬼主播率拒,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禁荒!你這毒婦竟也來了猬膨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對情侶失蹤呛伴,失蹤者是張志新(化名)和其女友劉穎勃痴,沒想到半個(gè)月后谒所,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沛申,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年劣领,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖当凡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情村生,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布固惯,位于F島的核電站梆造,受9級(jí)特大地震影響缴守,放射性物質(zhì)發(fā)生泄漏葬毫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一屡穗、第九天 我趴在偏房一處隱蔽的房頂上張望贴捡。 院中可真熱鬧,春花似錦村砂、人聲如沸烂斋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汛骂。三九已至,卻和暖如春评腺,著一層夾襖步出監(jiān)牢的瞬間帘瞭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工蒿讥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝶念,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓芋绸,卻偏偏與公主長得像媒殉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子摔敛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345