- Github地址:-CollectionViewLayout-CollectionViewFlowLayout-
- 這里詳解了三個(gè)demo去幫助大家更好的了解CollectionViewLayout和CollectionViewFlowLayout
- 自定義流水布局--CollectionViewFlowLayout---水平布局實(shí)現(xiàn)一個(gè)相冊(cè)功能
一
- 在UIScrollView的基礎(chǔ)上進(jìn)行循環(huán)利用
- 那怎么去做循環(huán)利用呢?
- 第一種方案:
- 實(shí)時(shí)監(jiān)控ScrollView的滾動(dòng),一旦有一個(gè)家伙離開(kāi)屏幕,我們就把它放進(jìn)一個(gè)數(shù)組或者是集合里面去,到時(shí)候我要用,我就把它拿過(guò)去用
- 但是這個(gè)是很麻煩的,因?yàn)槟憧偸堑门袛嗨袥](méi)有離開(kāi)屏幕
- 第二種方案:
- 用蘋(píng)果自帶的幾個(gè)類:TableView或者是CollectionView
- 因?yàn)樗鼈儽緛?lái)就具備循環(huán)利用的功能
- 但是TableView一看就不符合要求,因?yàn)樗J(rèn)就是上下豎直滾動(dòng),不是左右水平滾動(dòng)
- 當(dāng)然我們也可以用非主流的方式,讓TableView實(shí)現(xiàn)水平滾動(dòng)
- 讓TableView的Transform來(lái)個(gè)90°,讓它里面所有的cell也翻個(gè)90°,都轉(zhuǎn)過(guò)來(lái)。但這種做法有點(diǎn)奇葩,開(kāi)發(fā)中還是不要這么搞
- 所以我們可以用CollectionView
- CollectionView在我們的印象中是展示像那種九宮格的樣子,而且也是上下豎直滾動(dòng)
- 但是CollectionView和TableView的區(qū)別就是:
- CollectionView它默認(rèn)就支持水平滾動(dòng),你只要修改它一個(gè)屬性為水平方向就行了。而TableView默認(rèn)支持豎直滾動(dòng),沒(méi)有屬性去支持它水平滾動(dòng),除非你去搞一些非主流的做法
二
- CollectionView一定要傳一個(gè)不空的Layout那個(gè)參數(shù),因?yàn)槟J(rèn)的布局是九宮格,它按這種方式排的原因是它有一個(gè)流水布局。正因?yàn)榻o它傳了一個(gè)流水布局,所以它就一行滿了,就流向下一行,流水一樣流下去流過(guò)來(lái)
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:[UICollectionViewlayout alloc] init]];
- 數(shù)據(jù)源方法 - <UICollectionViewDataSource>
- numberOfItemsInSection是告訴它一組有多少個(gè)格子
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 50 ;
}
- cellForItemAtIndexPath告訴它每個(gè)格子長(zhǎng)出來(lái)是怎樣的一個(gè)cell,因?yàn)槊總€(gè)格子都是一個(gè)CollectionViewCell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 先要注冊(cè)
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYCellId forIndexPath:indexPath];
cell.backgroundColor = [UIColor orangeColor]
return cell;
}
- TableView和CollectionView的排布有很大的區(qū)別
- TableView的排布是一行一行往下排布,而CollectionView的排布是完全取決于Layout,也就是說(shuō),你傳給它的Layout不一樣,它的排布就不一樣。它的布局決定了cell的排布
- 也就是說(shuō),今后你想要CollectionView的cell排布豐富多彩,你只需要改變它的布局就行了
- scrollDirection決定了它的滾動(dòng)方向,設(shè)置它滾動(dòng)的方向?yàn)樗?/li>
// 水平滾動(dòng)
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- itemSize決定了CollectionView布局的里面的cell的大小
layout.itemSize = CGSizeMake(100, 100);
- 你將CollectionView高度改小點(diǎn),比如200,那么你的高度不夠顯示兩排,就會(huì)如下顯示:
- 而且你會(huì)發(fā)現(xiàn)不用擔(dān)心循環(huán)利用的問(wèn)題,CollectionView內(nèi)部已經(jīng)幫你做好了
三
- 我們現(xiàn)在已經(jīng)實(shí)現(xiàn)流水布局水平滾動(dòng),而且做好了循環(huán)利用。如果要做一層改進(jìn),那么我們就要自定義布局,自己來(lái)寫(xiě)一套布局,所以現(xiàn)在我們繼承于UICollectionViewFlowLayout
- 我們要自定義CollectionView的布局有兩種方案
- 1.繼承UICollectionViewLayout
- 一般是繼承于UICollectionViewLayout就行了
- 而且UICollectionViewFlowLayout繼承于UICollectionViewLayout
- 但是如果你自定義繼承于UICollectionViewLayout,代表著你沒(méi)有流水布局功能,也就是在你不想要流水布局功能的時(shí)候就選擇繼承UICollectionViewLayout
- 2.繼承UICollectionViewFlowLayout
- 1.繼承UICollectionViewLayout
**四 **
- 所以我們自定義流水布局CYLineLayout
- 在CYLineLayout.h文件中
#import <UIKit/UIKit.h>
@interface CYLineLayout : UICollectionViewFlowLayout
@end
- 在CYLineLayout.m文件中重寫(xiě)某些方法去實(shí)現(xiàn):
- 1.cell的放大與縮小
- 2.停止?jié)L動(dòng)的時(shí)候:cell居中
- 進(jìn)入頭文件可以發(fā)現(xiàn)要重寫(xiě)的一些方法
- UICollectionViewLayoutAttributes
- 1.它是描述布局屬性的
- 2.一個(gè)cell對(duì)應(yīng)一個(gè)UICollectionViewLayoutAttributes對(duì)象
- 3.UICollectionViewLayoutAttributes對(duì)象決定了cell的展示樣式(frame)說(shuō)白了就是決定你的cell擺在哪里,怎么去擺
- layoutAttributesForElementsInRect這個(gè)方法的返回值是一個(gè)數(shù)組(數(shù)組里面存放著rect范圍內(nèi)所有元素的布局屬性)
- 這個(gè)方法的返回值決定了rect范圍內(nèi)所有元素的排布(frame)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// 獲得super已經(jīng)計(jì)算好的布局屬性(在super已經(jīng)算好的基礎(chǔ)上,再去做一些改進(jìn))
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
// 在原有布局屬性的基礎(chǔ)上進(jìn)行微調(diào)
for (UICollectionViewLayoutAttributes *attrs in array) {
// cell的中心點(diǎn)x和collectionView最中心點(diǎn)的x值 的間距
CGFloat delta = ABS(attrs.center.x - centerX);
// 根據(jù)間距值計(jì)算cell的縮放比例
CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
// 設(shè)置縮放比例
attrs.transform = CGAffineTransformMakeScale(scale, scale);
}
return array;
}
- 計(jì)算collectionView中心點(diǎn)的x值
- 要記住collectionView的坐標(biāo)原點(diǎn)是以內(nèi)容contentSize的原點(diǎn)為原點(diǎn)
- 計(jì)算collectionView中心點(diǎn)的x值,千萬(wàn)不要用collectionView的寬度除以2。而是用collectionView的偏移量加上collectionView寬度的一半
- 坐標(biāo)原點(diǎn)弄錯(cuò)了就沒(méi)有可比性了,因?yàn)楹竺嬉袛郼ell的中心點(diǎn)與collectionView中心點(diǎn)的差值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
- cell的中心點(diǎn)x 和CollectionView最中心點(diǎn)的x值 的間距
CGFloat delta = ABS(attrs.center.x - centerX);
ABS(A)
// 表示取絕對(duì)值
- 我們?cè)俑鶕?jù)間距值delta去算cell的縮放比例scale
- 間距值delta和縮放比例scale是成反比的
- 間距值delta的范圍為0--self.collectionView.frame.size.width * 0.5
CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
// 用1-(),是因?yàn)殚g距值delta和縮放比例scale是成反比的
- 設(shè)置縮放比例
attrs.transform = CGAffineTransformMakeScale(scale, scale);
- 但是設(shè)置后你會(huì)發(fā)現(xiàn)基本沒(méi)啥反應(yīng),顯示還亂七八糟的,這是什么原因呢?
- 我們是想要稍微動(dòng)一下就修改一下,但是現(xiàn)在沒(méi)法達(dá)到我動(dòng)一下就根據(jù)最新的中心點(diǎn)X來(lái)再算一遍一邊比例。沒(méi)有實(shí)現(xiàn)這個(gè)代碼
- 因?yàn)檫@里還需要實(shí)現(xiàn)一個(gè)方法
- 這個(gè)方法是shouldInvalidateLayoutForBoundsChange: 它的特點(diǎn)是:
- 默認(rèn)return NO
- 當(dāng)collectionView的顯示范圍發(fā)生改變的時(shí)候,判斷是否需要重新刷新布局
- 一旦重新刷新布局,就會(huì)重新調(diào)用下面的方法:
- 1.prepareLayout
- 2.layoutAttributesForElementsInRect:方法
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- 這樣之后,你會(huì)發(fā)現(xiàn),你稍微挪一下,它就重新算一遍,比例就會(huì)縮放, 達(dá)到了我們的要求
- 而且非常流暢,因?yàn)樗醒h(huán)利用
五
- 還要實(shí)現(xiàn)一個(gè)方法:targetContentOffsetForProposedContentOffset:()方法。它的返回值,就決定了collectionView停止?jié)L動(dòng)時(shí)的偏移量
- 這個(gè)方法在你手離開(kāi)屏幕之前會(huì)調(diào)用,也就是cell即將停止?jié)L動(dòng)的時(shí)候 (記住這一點(diǎn))
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 計(jì)算出最終顯示的矩形框
CGRect rect;
rect.origin.y = 0;
rect.origin.x = proposedContentOffset.x;
rect.size = self.collectionView.frame.size;
// 獲得super已經(jīng)計(jì)算好的布局屬性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
// 存放最小的間距值
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attrs in array) {
if (ABS(minDelta) > ABS(attrs.center.x - centerX)) {
minDelta = attrs.center.x - centerX;
}
}
// 修改原有的偏移量
proposedContentOffset.x += minDelta;
return proposedContentOffset;
}
- 獲得super已經(jīng)計(jì)算好的布局屬性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
- 這里為什么不用self
- 因?yàn)槿绻{(diào)self,又會(huì)來(lái)到layoutAttributesForElementsInRect:()方法的for循環(huán)中, 將transform再算一遍。而我們只想要拿到中心點(diǎn)X值??扛割惥托辛?/li>
- 我們調(diào)super這個(gè)方法,因?yàn)樗?dāng)時(shí)已經(jīng)算好了cell的中心點(diǎn)等X的值了。所以這里調(diào)super可能更好一點(diǎn)
- 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
- 這里為什么不按前面
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
來(lái)算呢?
- 因?yàn)閠argetContentOffsetForProposedContentOffset:()方法在你手離開(kāi)屏幕之前會(huì)調(diào)用,也就是cell即將停止?jié)L動(dòng)的時(shí)候,這個(gè)時(shí)候我們要算的是最后停下來(lái)偏移量。
- 假如我們用力往左邊一甩,你的手已經(jīng)離開(kāi),算的偏移量是你手離開(kāi)時(shí)候的偏移量,而不是我們最終的偏移量,也就是說(shuō)這么算的話,我們就算錯(cuò)了
- 你是應(yīng)該拿到最終停下來(lái)的cell和CollectionView的中心點(diǎn)的X值進(jìn)行比較的。所以你應(yīng)該最終的值,而不是手松開(kāi)的那一刻的偏移量的值
- 那我們?cè)趺粗朗炙砷_(kāi)的那一刻最終的偏移量X的值呢?
- 這個(gè)方法返回的參數(shù)(CGPoint)proposedContentOffset,這是它本應(yīng)該停留的位置,最終停留的的值。而(CGPoint)targetContentOffsetForProposedContentOffset:這個(gè)是你最終返回的值,也就是你要它停留到哪兒的值(這個(gè)參數(shù)決定你要cell最后停留在哪兒)
- 那我們?cè)趺粗朗炙砷_(kāi)的那一刻最終的偏移量X的值呢?
- 同上面可知,我們最后拿到的矩形框也是不能亂傳的,也是要拿到最終的哪一個(gè)矩形框(不明白,就想像一下,你往左邊或者右邊用手指一甩的時(shí)候,手離開(kāi)的時(shí)候是一個(gè)值,最終停下來(lái)是一個(gè)值,而現(xiàn)在我們需要的是最終的值)
// 計(jì)算出最終顯示的矩形框
CGRect rect; rect.origin.y = 0; rect.origin.x = proposedContentOffset.x; rect.size = self.collectionView.frame.size;
- 然后我們要找最短的偏移量,找到它,然后就讓他偏移?它的那個(gè)值,讓它的中心點(diǎn)回到collectionView的中心點(diǎn),也就是說(shuō)重合。這樣就實(shí)現(xiàn)了不管你怎么去甩,等cell停下來(lái)的時(shí)候。都會(huì)有一個(gè)cell它會(huì)停留在矩形框CollectionView的中心
// 存放最小的間距值
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attrs in array) {
if (ABS(minDelta) > ABS(attrs.center.x - centerX)) {
minDelta = attrs.center.x - centerX;
}
}
// 修改原有的偏移量
proposedContentOffset.x += minDelta;
return proposedContentOffset;
- 一開(kāi)始先保證minDelta是最大的,保證誰(shuí)都比你小。 第一次算出來(lái)的絕對(duì)值就肯定比你小,然后把它賦值給你minDelta。?這樣就算出來(lái)了最小的間距值
- 算出來(lái)最小間距值后,你通過(guò)分析應(yīng)該會(huì)發(fā)現(xiàn),不管是往左偏還是往右偏,要想讓cell回到中心點(diǎn),最后你的偏移量應(yīng)該是用:你本來(lái)應(yīng)該 的偏移量+(cell的中心點(diǎn)X值—collectionView中心點(diǎn)X值)
- 所以上面在比較的時(shí)候用絕對(duì)值,計(jì)算的時(shí)候不用絕對(duì)值,minDelta最后就有正數(shù)也有負(fù)數(shù)
- 修改后讓它回到中間
- 最后不管你怎么滑,它都會(huì)停在中間
六
- 有一個(gè)小缺陷,你會(huì)發(fā)現(xiàn),一打開(kāi)程序,你往左或往右滑到最左或者最右的時(shí)候,cell總是默認(rèn)粘著邊上,這個(gè)不太和諧,我們需要它距離左右兩邊都有一個(gè)距離,那我們?cè)撛趺醋瞿兀?
- 這就是讓我們把所有的cell,讓它們往右邊或者左邊挪一段距離,所以就增加內(nèi)邊距就可以了。怎么添加內(nèi)邊距呢?
- collectionView是繼承ScrollView的,所以設(shè)置它的ContentInset就可以了
- 還一種方法通過(guò)這個(gè)布局它本來(lái)就有一個(gè)屬性sectionInset ,這本來(lái)就是來(lái)控制內(nèi)邊距的,控制整個(gè)布局的。而且這個(gè)屬性只需要設(shè)置一次
- 這就是讓我們把所有的cell,讓它們往右邊或者左邊挪一段距離,所以就增加內(nèi)邊距就可以了。怎么添加內(nèi)邊距呢?
- 這里有一個(gè)?給collectionView專門(mén)用來(lái)布局的方法---prepareLayout,這里一般是做初始化操作
/**
* 用來(lái)做布局的初始化操作(不建議在init方法中進(jìn)行布局的初始化操作--可能布局還未加到View中去,就會(huì)返回為空)
*/
- (void)prepareLayout
{
[super prepareLayout];
// 設(shè)置內(nèi)邊距
CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}
七
- 總的來(lái)說(shuō)我們?nèi)粢^承自這個(gè)流水布局來(lái)實(shí)現(xiàn)這個(gè)功能的話,肯定是要重寫(xiě)一些方法,告訴它一些內(nèi)部的行為,它才知道怎么去顯示那個(gè)東西,我們用了一下的方法:
- 我們首先得實(shí)現(xiàn)prepareLayout方法,做一些初始化
- 然后,我們實(shí)現(xiàn)layoutAttributesForElementsInRect:方法。目的是拿出它計(jì)算好的布局屬性來(lái)做一個(gè)微調(diào),這樣可以導(dǎo)致我們的cell可以變大或者變小
- 然后實(shí)現(xiàn)targetContentOffsetForProposedContentOffset:方法。它的目的是告訴當(dāng)我手松開(kāi),cell停止?jié)L動(dòng)的時(shí)候,他應(yīng)該去哪兒,所以這個(gè)方法就決定了collectionView停止?jié)L動(dòng)時(shí)的偏移量
- 最后shouldInvalidateLayoutForBoundsChange:這個(gè)方法的價(jià)值就是告訴它你只要稍微往左或者往右挪一下,你就重新刷新,只要你重新刷新,它就會(huì)重新根據(jù)你cell的中心點(diǎn)的X值距離你collectionView中心點(diǎn)的X值來(lái)決定你的縮放比例。這樣就保證了我們每動(dòng)一點(diǎn)點(diǎn),比例都在變,所以我們要?jiǎng)右幌滤⑿乱幌隆R簿褪钱?dāng)collectionView的顯示范圍發(fā)生改變的時(shí)候,是否需要重新刷新布局,一旦重新刷新布局,就會(huì)重新調(diào)用下面的方法:1.prepareLayout2.layoutAttributesForElementsInRect:方法
- 關(guān)于做這個(gè)效果有一個(gè)挺牛逼的三方框架:iCarousel大家可以參考一下
八
- 在CYLineLayout.h文件中
#import <UIKit/UIKit.h>
@interface CYLineLayout : UICollectionViewFlowLayout
@end
- 在CYLineLayout.h文件中
#import "CYLineLayout.h"
@implementation CYLineLayout
- (instancetype)init
{
if (self = [super init]) {
}
return self;
}
/**
* 當(dāng)collectionView的顯示范圍發(fā)生改變的時(shí)候,是否需要重新刷新布局
* 一旦重新刷新布局,就會(huì)重新調(diào)用下面的方法:
1.prepareLayout
2.layoutAttributesForElementsInRect:方法
*/
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
/**
* 用來(lái)做布局的初始化操作(不建議在init方法中進(jìn)行布局的初始化操作)
*/
- (void)prepareLayout
{
[super prepareLayout];
// 水平滾動(dòng)
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// 設(shè)置內(nèi)邊距
CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}
/**
UICollectionViewLayoutAttributes *attrs;
1.一個(gè)cell對(duì)應(yīng)一個(gè)UICollectionViewLayoutAttributes對(duì)象
2.UICollectionViewLayoutAttributes對(duì)象決定了cell的frame
*/
/**
* 這個(gè)方法的返回值是一個(gè)數(shù)組(數(shù)組里面存放著rect范圍內(nèi)所有元素的布局屬性)
* 這個(gè)方法的返回值決定了rect范圍內(nèi)所有元素的排布(frame)
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// 獲得super已經(jīng)計(jì)算好的布局屬性
NSArray *array = [super layoutAttributesForElementsInRect:rect] ;
// 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
// 在原有布局屬性的基礎(chǔ)上,進(jìn)行微調(diào)
for (UICollectionViewLayoutAttributes *attrs in array) {
// cell的中心點(diǎn)x 和 collectionView最中心點(diǎn)的x值 的間距
CGFloat delta = ABS(attrs.center.x - centerX);
// 根據(jù)間距值 計(jì)算 cell的縮放比例
CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
// 設(shè)置縮放比例
attrs.transform = CGAffineTransformMakeScale(scale, scale);
}
return array;
}
/**
* 這個(gè)方法的返回值,就決定了collectionView停止?jié)L動(dòng)時(shí)的偏移量
*/
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 計(jì)算出最終顯示的矩形框
CGRect rect;
rect.origin.y = 0;
rect.origin.x = proposedContentOffset.x;
rect.size = self.collectionView.frame.size;
// 獲得super已經(jīng)計(jì)算好的布局屬性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
// 存放最小的間距值
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attrs in array) {
if (ABS(minDelta) > ABS(attrs.center.x - centerX)) {
minDelta = attrs.center.x - centerX;
}
}
// 修改原有的偏移量
proposedContentOffset.x += minDelta;
return proposedContentOffset;
}
@end
九
- 假如我們要監(jiān)聽(tīng)cell的點(diǎn)擊,要怎么辦呢?上面這講的這些都和CollectionViewCell的點(diǎn)擊沒(méi)有關(guān)系,只是和布局有關(guān)。監(jiān)聽(tīng)CollectionViewCell的點(diǎn)擊和CollectionViewCell的布局沒(méi)有任何關(guān)系,布局只負(fù)責(zé)展示,格子里面是什么內(nèi)容,還是取決于cell
- 布局的作用僅僅是控制cell的排布
- 控制器先成為CollectionViewCell的代理:UICollectionViewDelegate
- 現(xiàn)在要把數(shù)據(jù)填充上去,讓它顯示相冊(cè)了,所以自定義CollectionViewCell--CYPhotoCell,由于里面是固定死的,所以加一個(gè)Xib文件,里面加一個(gè)ImageView,拖線給一個(gè)屬性,給ImageView一個(gè)標(biāo)識(shí)photo
- 給cell里面的相片加上一個(gè)相冊(cè)相框的效果--兩種方案:
- 第一種方案:在Xib的ImageView的布局上下左右都給一個(gè)10的間距,給一個(gè)white的背景顏色
- 第二種方案:給我們的ImageView加一個(gè)圖層就可以了
- (void)awakeFromNib {
self.imageView.layer.borderColor = [UIColor whiteColor].CGColor;
self.imageView.layer.borderWidth = 10;
}
- 在CYPhotoCell.h文件中
#import <UIKit/UIKit.h>
@interface CYPhotoCell : UICollectionViewCell
/** 圖片名 */
@property (nonatomic, copy) NSString *imageName;
@end
- 在CYPhotoCell.m文件中
#import "CYPhotoCell.h"
@interface CYPhotoCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation CYPhotoCell
- (void)awakeFromNib {
self.imageView.layer.borderColor = [UIColor whiteColor].CGColor;
self.imageView.layer.borderWidth = 10;
}
- (void)setImageName:(NSString *)imageName
{
_imageName = [imageName copy];
self.imageView.image = [UIImage imageNamed:imageName];
}
@end
- 在ViewController.m文件中
#import "ViewController.h"
#import "CYLineLayout.h"
#import "CYPhotoCell.h"
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
@end
@implementation ViewController
static NSString * const CYPhotoId = @"photo";
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建布局
CYLineLayout *layout = [[CYLineLayout alloc] init];
layout.itemSize = CGSizeMake(100, 100);
// 創(chuàng)建CollectionView
CGFloat collectionW = self.view.frame.size.width;
CGFloat collectionH = 200;
CGRect frame = CGRectMake(0, 150, collectionW, collectionH);
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
collectionView.dataSource = self;
collectionView.delegate = self;
[self.view addSubview:collectionView];
// 注冊(cè)
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYPhotoCell class]) bundle:nil] forCellWithReuseIdentifier:CYPhotoId];
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 20;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CYPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYPhotoId forIndexPath:indexPath];
cell.imageName = [NSString stringWithFormat:@"%zd", indexPath.item + 1];
return cell;
}
#pragma mark - <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"------%zd", indexPath.item);
}
@end
-
最后就實(shí)現(xiàn)了:
十
自定義流水布局
自定義布局 - 繼承UICollectionViewFlowLayout
-
重寫(xiě)prepareLayout方法
- 作用:
- 在這個(gè)方法中做一些初始化操作 - 注意:
- 一定要調(diào)用[super prepareLayout]
- 作用:
-
重寫(xiě)layoutAttributesForElementsInRect:方法
- 作用:
- 這個(gè)方法的返回值是個(gè)數(shù)組
- 這個(gè)數(shù)組中存放的都是UICollectionViewLayoutAttributes對(duì)象
- UICollectionViewLayoutAttributes對(duì)象決定了cell的排布方式(frame等)
- 作用:
-
重寫(xiě)shouldInvalidateLayoutForBoundsChange:方法
- 作用:
- 如果返回YES,那么collectionView顯示的范圍發(fā)生改變時(shí),就會(huì)重新刷新布局 - 一旦重新刷新布局,就會(huì)按順序調(diào)用下面的方法:
- prepareLayout
- layoutAttributesForElementsInRect:
- 作用:
-
重寫(xiě)targetContentOffsetForProposedContentOffset:方法
- 作用:
- 返回值決定了collectionView停止?jié)L動(dòng)時(shí)最終的偏移量(contentOffset) - 參數(shù):
- proposedContentOffset:原本情況下,collectionView停止?jié)L動(dòng)時(shí)最終的偏移量
- velocity:滾動(dòng)速率,通過(guò)這個(gè)參數(shù)可以了解滾動(dòng)的方向(根據(jù)X和Y的正負(fù))
- 作用:
自定義布局--CollectionViewLayout--格子布局
- 分析一下這個(gè)布局的排布是有規(guī)律的:
- 這里的相冊(cè)布局和上面的流水布局不同
- 我們較上面的不需要更改太多東西,只是修改它的布局方式就行了
- 六個(gè)為一組
-
對(duì)應(yīng)cell相差兩個(gè)高度
- 一個(gè)這樣的布局如何實(shí)現(xiàn)?
- 首先這里不不好用流水布局,流水布局的ItemSize是一樣大的
- 肯定也牽扯到了循環(huán)利用,所以仍然用CollectionView,?所以就用一個(gè)?最根的布局--CollectionViewLayout
- CollectionViewLayout它不像流水布局,內(nèi)部沒(méi)有任何方法給你去排,所以你只有繼承自它,然后自己去寫(xiě)一套排布方式,排布是由我們來(lái)算
- 將上面文件中的CYLineLayout刪除,New一個(gè)File--CYGridLayout繼承自CollectionViewLayout
// 創(chuàng)建UICollectionViewLayoutAttributes
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
- 說(shuō)白了我這個(gè)UICollectionViewLayoutAttributes是描述一個(gè)cell用的
- indexPath代表了對(duì)應(yīng)某個(gè)位置的cell,也就是說(shuō)我這個(gè)UICollectionViewLayoutAttributes是描述哪個(gè)位置的cell
- 通過(guò)觀察可以發(fā)現(xiàn)規(guī)律
- 在ViewController.m文件中修改一下collectionView的frame和布局
#import "ViewController.h"
#import "CYGridLayout.h"
#import "CYPhotoCell.h"
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
@end
@implementation ViewController
static NSString * const CYPhotoId = @"photo";
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建布局
CYGridLayout *layout = [[CYGridLayout alloc] init];
// 創(chuàng)建CollectionView
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
collectionView.dataSource = self;
collectionView.delegate = self;
[self.view addSubview:collectionView];
// 注冊(cè)
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYPhotoCell class]) bundle:nil] forCellWithReuseIdentifier:CYPhotoId];
}
- CYGridLayout里面去實(shí)現(xiàn)collectionView具體的布局
- 在CYGridLayout.m文件中
#import "CYGridLayout.h"
@interface CYGridLayout()
/** 所有的布局屬性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
@end
@implementation CYGridLayout
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
- (void)prepareLayout
{
[super prepareLayout];
[self.attrsArray removeAllObjects];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i++) {
// 創(chuàng)建UICollectionViewLayoutAttributes
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 設(shè)置布局屬性
CGFloat width = self.collectionView.frame.size.width * 0.5;
if (i == 0) {
CGFloat height = width;
CGFloat x = 0;
CGFloat y = 0;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 1) {
CGFloat height = width * 0.5;
CGFloat x = width;
CGFloat y = 0;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 2) {
CGFloat height = width * 0.5;
CGFloat x = width;
CGFloat y = height;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 3) {
CGFloat height = width * 0.5;
CGFloat x = 0;
CGFloat y = width;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 4) {
CGFloat height = width * 0.5;
CGFloat x = 0;
CGFloat y = width + height;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 5) {
CGFloat height = width;
CGFloat x = width;
CGFloat y = width;
attrs.frame = CGRectMake(x, y, width, height);
} else {
UICollectionViewLayoutAttributes *lastAttrs = self.attrsArray[i - 6];
CGRect lastFrame = lastAttrs.frame;
lastFrame.origin.y += 2 * width;
attrs.frame = lastFrame;
}
// 添加UICollectionViewLayoutAttributes
[self.attrsArray addObject:attrs];
}
}
- 運(yùn)行程序:
- 你會(huì)發(fā)現(xiàn)無(wú)法使它往上滾動(dòng),這是為啥呢?
- 因?yàn)槟悻F(xiàn)在時(shí)繼承自最根本的布局CollectionViewLayout,很多東西是得自己去設(shè)置了才會(huì)有,來(lái)到頭文件,你會(huì)發(fā)現(xiàn)
- 要重寫(xiě)它的(CGSize)collectionViewContentSize方法,告訴它你這個(gè)CollectionView的內(nèi)容尺寸,來(lái)決定它怎么滾。所以你現(xiàn)在無(wú)法滾動(dòng)是因?yàn)镃ollectionView的ContentSize沒(méi)有確定
/**
* 返回collectionView的內(nèi)容大小
*/
- (CGSize)collectionViewContentSize
{
int count = (int)[self.collectionView numberOfItemsInSection:0];
int rows = (count + 3 - 1) / 3;
CGFloat rowH = self.collectionView.frame.size.width * 0.5;
return CGSizeMake(0, rows * rowH);
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
- 這里在性能優(yōu)化上是還有點(diǎn)小問(wèn)題的,因?yàn)槲覀円豢跉獍阉袞|西都算完了。你如果覺(jué)得費(fèi)時(shí),完全可以把計(jì)算放在子線程中,然后返回到主線程刷新UI(CollectionViewLayout布局中有一個(gè)刷新方法,你調(diào)一下就行了)
- 計(jì)算不是重點(diǎn),你是可以總結(jié)出計(jì)算的規(guī)律的。重點(diǎn)是:繼承自CollectionViewLayout你需要注意什么?
- 1.一旦你重寫(xiě)了layoutAttributesForElementsInRect這個(gè)方法,就意味著所有東西你得自己寫(xiě)了,你的Attributes對(duì)象得自己創(chuàng)建了,因?yàn)樗母割惒粫?huì)幫你創(chuàng)建
- 2.一旦你繼承自CollectionViewLayout,意味著你這個(gè)collectionViewContentSize都得告訴它了,這個(gè)是得你自己去算的
- 3.如果你是希望一口氣把所有東西算完,不希望它在滾動(dòng)過(guò)程中再算,你可以在prepareLayout方法里面先算清楚,算完后盡管它傳的矩形框都不一樣,但是我返回的還是同一份。
-
?這里給?出一個(gè)思想:
- 以后,你凡事?tīng)砍兜絻?nèi)容是很多很多的,你想做什么循環(huán)利用,而且布局又亂七八糟的,我們用CollectionViewLayout就可以了。我們只有繼承自這個(gè)CollectionViewLayout,然后我們實(shí)現(xiàn)layoutAttributesForElementsInRect這個(gè)方法,在那里去告訴它,你的cell怎么去排。并且繼承自CollectionViewLayout,意味著很多東西都要重寫(xiě),如:collectionViewContentSize
-
這樣就實(shí)現(xiàn)了:
自定義布局--CollectionViewLayout--布局之間的切換
- 要求:
- 實(shí)現(xiàn)一個(gè)環(huán)形布局和水平布局的相冊(cè),點(diǎn)擊屏幕能夠進(jìn)行不同布局之間的切換
- 點(diǎn)擊cell的時(shí)候可以刪除cell
- 首先通過(guò)分析,在上面第一個(gè)案例的基礎(chǔ)上,再添加一個(gè)環(huán)形布局--CYCircleLayout,肯定也是只能繼承自CollectionViewLayout
- 在這里CYCircleLayout里面就只需要實(shí)現(xiàn)prepareLayout方法和layoutAttributesForElementsInRect方法,不需再要重寫(xiě)實(shí)現(xiàn)collectionViewContentSize的方法,因?yàn)樗恍枰獫L動(dòng),所以CollectionViewLayout里面所有方法的實(shí)現(xiàn)是看你的需求的
#import "CYCircleLayout.h"
@interface CYCircleLayout()
/** 布局屬性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
@end
@implementation CYCircleLayout
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
- (void)prepareLayout
{
[super prepareLayout];
[self.attrsArray removeAllObjects];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
- 我們可以看出,每個(gè)相片cell的中心點(diǎn)都在一個(gè)圓上,所以我們要將它擺正,肯定不是設(shè)置它們的frame,而是去設(shè)置它c(diǎn)enter這個(gè)值,我只要保證它的center那個(gè)值在那個(gè)圓上就可以了
- 也就是說(shuō)我們要算出每個(gè)相片cell的中心點(diǎn)的X和Y值,通過(guò)中心點(diǎn)來(lái)布局它,而不是通過(guò)frame的original的X和Y(這樣太麻煩,不好算)
- 這里我們只要確定圓心就好算了
- 圓心(X和Y值分別是CollectionView寬度和高度的一半)
- 而且每張相片的中心點(diǎn)距離圓心的距離為半徑
- 你會(huì)發(fā)現(xiàn)每個(gè)相片cell的中心點(diǎn)的X,Y和圓心的X,Y之間的差值是有規(guī)律的:
- Y值--圓心點(diǎn)的Y值-(Y*cosa)= cell的Y值,X值同樣道理去算
- 角度a的大小取決于cell的個(gè)數(shù)(假如20個(gè)cell--->a = 360° / 20)
- 所以我們只要算出平分角度就行了
- 比如說(shuō)第一個(gè)cell為索引0,角度就是0,第二個(gè)為索引1,角度就是a, 第三個(gè)為索引2,角度就是a2......第i個(gè)為索引i-1,角度就是a(i-1 )
- 于是乎
- 這里記住:如果你是繼承自CollectionViewLayout,如果你要換布局話,有一個(gè)方法是一定得實(shí)現(xiàn)的--layoutAttributesForItemAtIndexPath:方法。只有繼承CollectionViewLayout才需要,流水布局不需要,因?yàn)榱魉季謨?nèi)部早已經(jīng)幫你實(shí)現(xiàn)了這個(gè)方法
/**
* 這個(gè)方法需要返回indexPath位置對(duì)應(yīng)cell的布局屬性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger count = [self.collectionView numberOfItemsInSection:0];
CGFloat radius = 70;
// 圓心的位置
CGFloat oX = self.collectionView.frame.size.width * 0.5;
CGFloat oY = self.collectionView.frame.size.height * 0.5;
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.size = CGSizeMake(50, 50);
if (count == 1) {
attrs.center = CGPointMake(oX, oY);
} else {
CGFloat angle = (2 * M_PI / count) * indexPath.item;
CGFloat centerX = oX + radius * sin(angle);
CGFloat centerY = oY + radius * cos(angle);
attrs.center = CGPointMake(centerX, centerY);
}
return attrs;
}
- 點(diǎn)擊屏幕切換布局
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([self.collectionView.collectionViewLayout isKindOfClass:[CYLineLayout class]]) {
[self.collectionView setCollectionViewLayout:[[CYCircleLayout alloc] init] animated:YES];
} else {
CYLineLayout *layout = [[CYLineLayout alloc] init];
layout.itemSize = CGSizeMake(100, 100);
[self.collectionView setCollectionViewLayout:layout animated:YES];
}
}
- 點(diǎn)擊cell就把cell刪掉
- 這里要注意的是:
- 你要把cell刪掉了,對(duì)應(yīng)的模型或者說(shuō) 數(shù)據(jù)也是得改變的
- 這里要注意的是:
- 可變數(shù)組,先把所有圖片名放進(jìn)去
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
/** collectionView */
@property (nonatomic, weak) UICollectionView *collectionView;
/** 數(shù)據(jù) */
@property (nonatomic, strong) NSMutableArray *imageNames;
@end
@implementation ViewController
static NSString * const CYPhotoId = @"photo";
- (NSMutableArray *)imageNames
{
if (!_imageNames) {
_imageNames = [NSMutableArray array];
for (int i = 0; i<20; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"%zd", i + 1]];
}
}
return _imageNames;
}
- 數(shù)據(jù)源里面的東西也是得改變的
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.imageNames.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CYPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYPhotoId forIndexPath:indexPath];
cell.imageName = self.imageNames[indexPath.item];
return cell;
}
- 你要把cell刪掉,也得保證把模型也刪掉了(不可能你cell刪掉了,數(shù)據(jù)還是這么多,那就出問(wèn)題了)
#pragma mark - <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
[self.imageNames removeObjectAtIndex:indexPath.item];
[self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
}
- 刪除到最后一個(gè)的時(shí)候,讓最后一個(gè)cell的位置來(lái)到圓心
if (count == 1) {
attrs.center = CGPointMake(oX, oY);
} else {
CGFloat angle = (2 * M_PI / count) * indexPath.item;
CGFloat centerX = oX + radius * sin(angle);
CGFloat centerY = oY + radius * cos(angle);
attrs.center = CGPointMake(centerX, centerY);
}
- 這樣所有的邏輯就理清楚了
- 在CYCircleLayout.m文件中
#import "CYCircleLayout.h"
@interface CYCircleLayout()
/** 布局屬性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
@end
@implementation CYCircleLayout
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
- (void)prepareLayout
{
[super prepareLayout];
[self.attrsArray removeAllObjects];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 這個(gè)方法需要返回indexPath位置對(duì)應(yīng)cell的布局屬性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger count = [self.collectionView numberOfItemsInSection:0];
CGFloat radius = 70;
// 圓心的位置
CGFloat oX = self.collectionView.frame.size.width * 0.5;
CGFloat oY = self.collectionView.frame.size.height * 0.5;
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.size = CGSizeMake(50, 50);
if (count == 1) {
attrs.center = CGPointMake(oX, oY);
} else {
CGFloat angle = (2 * M_PI / count) * indexPath.item;
CGFloat centerX = oX + radius * sin(angle);
CGFloat centerY = oY + radius * cos(angle);
attrs.center = CGPointMake(centerX, centerY);
}
return attrs;
}
@end
- 在ViewController.m文件中
#import "ViewController.h"
#import "CYLineLayout.h"
#import "CYCircleLayout.h"
#import "CYPhotoCell.h"
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
/** collectionView */
@property (nonatomic, weak) UICollectionView *collectionView;
/** 數(shù)據(jù) */
@property (nonatomic, strong) NSMutableArray *imageNames;
@end
@implementation ViewController
static NSString * const CYPhotoId = @"photo";
- (NSMutableArray *)imageNames
{
if (!_imageNames) {
_imageNames = [NSMutableArray array];
for (int i = 0; i<20; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"%zd", i + 1]];
}
}
return _imageNames;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建布局
CYCircleLayout *layout = [[CYCircleLayout alloc] init];
// 創(chuàng)建CollectionView
CGFloat collectionW = self.view.frame.size.width;
CGFloat collectionH = 200;
CGRect frame = CGRectMake(0, 150, collectionW, collectionH);
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
collectionView.dataSource = self;
collectionView.delegate = self;
[self.view addSubview:collectionView];
self.collectionView = collectionView;
// 注冊(cè)
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYPhotoCell class]) bundle:nil] forCellWithReuseIdentifier:CYPhotoId];
// 繼承UICollectionViewLayout
// 繼承UICollectionViewFlowLayout
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([self.collectionView.collectionViewLayout isKindOfClass:[CYLineLayout class]]) {
[self.collectionView setCollectionViewLayout:[[CYCircleLayout alloc] init] animated:YES];
} else {
CYLineLayout *layout = [[CYLineLayout alloc] init];
layout.itemSize = CGSizeMake(100, 100);
[self.collectionView setCollectionViewLayout:layout animated:YES];
}
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.imageNames.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CYPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYPhotoId forIndexPath:indexPath];
cell.imageName = self.imageNames[indexPath.item];
return cell;
}
#pragma mark - <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
[self.imageNames removeObjectAtIndex:indexPath.item];
[self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
}
@end
-
這樣就實(shí)現(xiàn)了
如果覺(jué)得對(duì)你有幫助,?Give me a star