UICollectionView的使用
UITableView中我們使用datasource和delegate分別處理我們的數(shù)據(jù)和交互,而且UITableView默認(rèn)提供了兩種樣式供我們選擇如何呈現(xiàn)數(shù)據(jù),在IOS6中蘋果提供了UICollectionView用來更自由地定制呈現(xiàn)我們的數(shù)據(jù)。
UICollectionView使用包括三個(gè)部分:
1.設(shè)置數(shù)據(jù)(使用UICollectionViewDataSource)
2.設(shè)置數(shù)據(jù)呈現(xiàn)方式(使用UICollectionViewLayout)
3.設(shè)置界面交互(使用UICollectionViewDelegate)
其中1,3和UITableView一致,可見UICollectionView比UITableView更具有一般性(我們可以使用UICollectionView實(shí)現(xiàn)UITableView的效果)
本篇博客的outline如下(本文參考http://www.onevcat.com/2012/06/introducing-collection-views/,代碼下載地址為https://github.com/zanglitao/UICollectionViewDemo)
1:基本介紹
2:UICollectionViewDataSource和UICollectionViewDelegate介紹
3:使用UICollectionViewFlowLayout
4:UICollectionViewFlowLayout的擴(kuò)展
5:使用自定義UICollectionViewLayout
6:添加和刪除數(shù)據(jù)
7:布局切換
?
基本介紹
UICollectionView是一種新的數(shù)據(jù)展示方式,簡單來說可以把他理解成多列的UITableView(請一定注意這是UICollectionView的最最簡單的形式)。如果你用過iBooks的話,可能你還對書架布局有一定印象:一個(gè)虛擬書架上放著你下載和購買的各類圖書,整齊排列。其實(shí)這就是一個(gè)UICollectionView的表現(xiàn)形式,或者iPad的iOS6中的原生時(shí)鐘應(yīng)用中的各個(gè)時(shí)鐘,也是UICollectionView的最簡單的一個(gè)布局,如圖:
?最簡單的UICollectionView就是一個(gè)GridView,可以以多列的方式將數(shù)據(jù)進(jìn)行展示。標(biāo)準(zhǔn)的UICollectionView包含三個(gè)部分,它們都是UIView的子類:
- Cells 用于展示內(nèi)容的主體,對于不同的cell可以指定不同尺寸和不同的內(nèi)容,這個(gè)稍后再說
- Supplementary Views 追加視圖 如果你對UITableView比較熟悉的話,可以理解為每個(gè)Section的Header或者Footer,用來標(biāo)記每個(gè)section的view
- Decoration Views 裝飾視圖 這是每個(gè)section的背景,比如iBooks中的書架就是這個(gè)
?
不管一個(gè)UICollectionView的布局如何變化,這三個(gè)部件都是存在的。再次說明,復(fù)雜的UICollectionView絕不止上面的幾幅圖。
?
UICollectionViewDataSource和UICollectionViewDelegate介紹
UICollectionViewDataSource用來設(shè)置數(shù)據(jù),此協(xié)議包含的方法如下
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section; //設(shè)置每個(gè)section包含的item數(shù)目- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; //返回對應(yīng)indexPath的cell- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView; //返回section的數(shù)目,此方法可選,默認(rèn)返回1- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; //返回Supplementary Views,此方法可選?
對于Decoration Views,提供方法并不在UICollectionViewDataSource中,而是直接UICollectionViewLayout類中的(因?yàn)樗鼉H僅是視圖相關(guān),而與數(shù)據(jù)無關(guān)),放到稍后再說。
與UITableViewCell相似的是UICollectionViewCell也支持重用,典型的UITbleViewCell重用寫法如下
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MY_CELL_ID"]; if (!cell) { //如果沒有可重用的cell,那么生成一個(gè) cell = [[UITableViewCell alloc] init]; } //配置cell,blablabla return cell?
UICollectionViewCell重用寫法于UITableViewCell一致,但是現(xiàn)在更簡便的是如果我們直接在storyboard中對cell設(shè)置了identifier,或者使用了以下方法進(jìn)行注冊
- -registerClass:forCellWithReuseIdentifier:
- -registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
- -registerNib:forCellWithReuseIdentifier:
- -registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
那么可以更簡單地實(shí)現(xiàn)重用
- (UICollectionView*)collectionView:(UICollectionView*)cv cellForItemAtIndexPath:(NSIndexPath*)indexPath { MyCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@”MY_CELL_ID”]; // Configure the cell's content cell.imageView.image = ... return cell; }上面的4個(gè)語句分別提供了nib和class方法對collectionViewCell和supplementaryView進(jìn)行注冊
?
UICollectionViewDelegate處理交互,包括cell點(diǎn)擊事件,cell點(diǎn)擊后高亮效果以及長按菜單等設(shè)置,當(dāng)用戶點(diǎn)擊cell后,會依次執(zhí)行協(xié)議中以下方法
狀態(tài)控制要比以前靈活一些,對應(yīng)的高亮和選中狀態(tài)分別由highlighted和selected兩個(gè)屬性表示。
關(guān)于Cell
相對于UITableViewCell來說,UICollectionViewCell沒有這么多花頭。首先UICollectionViewCell不存在各式各樣的默認(rèn)的style,這主要是由于展示對象的性質(zhì)決定的,因?yàn)閁ICollectionView所用來展示的對象相比UITableView來說要來得靈活,大部分情況下更偏向于圖像而非文字,因此需求將會千奇百怪。因此SDK提供給我們的默認(rèn)的UICollectionViewCell結(jié)構(gòu)上相對比較簡單,由下至上:
- 首先是cell本身作為容器view
- 然后是一個(gè)大小自動適應(yīng)整個(gè)cell的backgroundView,用作cell平時(shí)的背景
- 再其上是selectedBackgroundView,是cell被選中時(shí)的背景
- 最后是一個(gè)contentView,自定義內(nèi)容應(yīng)被加在這個(gè)view上
這次Apple給我們帶來的好康是被選中cell的自動變化,所有的cell中的子view,也包括contentView中的子view,在當(dāng)cell被選中時(shí),會自動去查找view是否有被選中狀態(tài)下的改變。比如在contentView里加了一個(gè)normal和selected指定了不同圖片的imageView,那么選中這個(gè)cell的同時(shí)這張圖片也會從normal變成selected,而不需要額外的任何代碼。
?
使用UICollectionViewFlowLayout
UICollectionViewLayout用來處理數(shù)據(jù)的布局,通過它我們可以設(shè)置每個(gè)cell,Supplementary View以及Decoration Views的呈現(xiàn)方式,比如位置,大小,透明度,形狀等等屬性
Layout決定了UICollectionView是如何顯示在界面上的。在展示之前,一般需要生成合適的UICollectionViewLayout子類對象,并將其賦予CollectionView的collectionViewLayout屬性,蘋果還提供了一個(gè)現(xiàn)成的UICollectionViewFlowLayout,通過這個(gè)layout我們可以很簡單地實(shí)現(xiàn)流布局,UICollectionViewFlowLayout常用的配置屬性如下
- CGSize itemSize:它定義了每一個(gè)item的大小。通過設(shè)定itemSize可以全局地改變所有cell的尺寸,如果想要對某個(gè)cell制定尺寸,可以使用-collectionView:layout:sizeForItemAtIndexPath:方法。
- CGFloat minimumLineSpacing:每一行的間距
- CGFloat minimumInteritemSpacing:item與item的間距
- UIEdgeInsets sectionInset:每個(gè)section的縮進(jìn)
- UICollectionViewScrollDirection scrollDirection:設(shè)定是垂直流布局還是橫向流布局,默認(rèn)是UICollectionViewScrollDirectionVertical
- CGSize headerReferenceSize:設(shè)定header尺寸
- CGSize footerReferenceSize:設(shè)定footer尺寸
上面都是全局屬性的設(shè)置,我們可以通過delegate中的方法對進(jìn)行定制,通過實(shí)現(xiàn)以下這些方法設(shè)定的屬性的優(yōu)先級比全局設(shè)定的要高
@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate> @optional- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;@end?
接下來我們使用使用UICollectionViewFlowLayout完成一個(gè)簡單demo
1:設(shè)置我們的cell
//SimpleFlowLayoutCell.h @interface SimpleFlowLayoutCell : UICollectionViewCell @property(nonatomic,strong)UILabel *label; @end//SimpleFlowLayoutCell.m @implementation SimpleFlowLayoutCell-(id)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {self.label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, frame.size.width, frame.size.height)];self.label.textAlignment = NSTextAlignmentCenter;self.label.textColor = [UIColor blackColor];self.label.font = [UIFont boldSystemFontOfSize:15.0];self.backgroundColor = [UIColor lightGrayColor];[self.contentView addSubview:self.label];self.contentView.layer.borderWidth = 1.0f;self.contentView.layer.borderColor = [UIColor blackColor].CGColor;}return self; }@end2:設(shè)置追加視圖
//SimpleFlowLayoutSupplementaryView.h @interface SimpleFlowLayoutSupplementaryView : UICollectionReusableView @property(nonatomic,strong)UILabel *label; @end//SimpleFlowLayoutSupplementaryView.m @implementation SimpleFlowLayoutSupplementaryView-(id)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {self.label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, frame.size.width, frame.size.height)];self.label.textAlignment = NSTextAlignmentCenter;self.label.textColor = [UIColor blackColor];self.label.font = [UIFont boldSystemFontOfSize:15.0];self.backgroundColor = [UIColor lightGrayColor];[self addSubview:self.label];self.layer.borderWidth = 1.0f;self.layer.borderColor = [UIColor blackColor].CGColor;}return self; }@end?
3:使用流布局初始化我們的UICollectionView
- (void)viewDidLoad {[super viewDidLoad];self.collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:[[UICollectionViewFlowLayout alloc] init]];self.collectionView.backgroundColor = [UIColor whiteColor];self.collectionView.delegate = self;self.collectionView.dataSource = self;[self.collectionView registerClass:[SimpleFlowLayoutCell class] forCellWithReuseIdentifier:@"MY_CELL"];//追加視圖的類型是UICollectionElementKindSectionHeader,也可以設(shè)置為UICollectionElementKindSectionFooter[self.collectionView registerClass:[SimpleFlowLayoutSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_SUPPLEMENT"];[self.view addSubview:self.collectionView]; }?
4:配置datasource
//每個(gè)section中有32個(gè)item - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {return 32; }- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {SimpleFlowLayoutCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellidentifier forIndexPath:indexPath];cell.label.text = [NSString stringWithFormat:@"%d",indexPath.item];return cell; }- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {return 2; }// The view that is returned must be retrieved from a call to -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {SimpleFlowLayoutSupplementaryView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_SUPPLEMENT" forIndexPath:indexPath];view.label.text = [NSString stringWithFormat:@"section header %d",indexPath.section];return view; }此時(shí)運(yùn)行程序可以看到如下界面
?程序并沒有顯示我們設(shè)置的header視圖,這是因?yàn)槲覀兪褂玫氖荱ICollectionViewFlowLayout默認(rèn)配置,當(dāng)前header視圖高度為0,我們可以通過設(shè)置UICollectionViewFlowLayout的
headerReferenceSize屬性改變大小,也可以通過協(xié)議方法返回特定section的header大小,這里我們先使用后者
我們添加以下方法
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {return CGSizeMake(44, 44); }此時(shí)再運(yùn)行就能得到以下結(jié)果
?
5:配置layout
上面的代碼使用了flowlayout默認(rèn)的配置,包括itemsize,行間距,item間距,追加視圖大小等等都是默認(rèn)值,我們可以改變這些值
- (void)viewDidLoad {[super viewDidLoad];UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];self.collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];self.collectionView.backgroundColor = [UIColor whiteColor];self.collectionView.delegate = self;self.collectionView.dataSource = self;[self.collectionView registerClass:[SimpleFlowLayoutCell class] forCellWithReuseIdentifier:@"MY_CELL"];//追加視圖的類型是UICollectionElementKindSectionHeader,也可以設(shè)置為UICollectionElementKindSectionFooter[self.collectionView registerClass:[SimpleFlowLayoutSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_SUPPLEMENT"];[self.view addSubview:self.collectionView];//配置UICollectionViewFlowLayout屬性//每個(gè)itemsize的大小layout.itemSize = CGSizeMake(80, 50);//行與行的最小間距layout.minimumLineSpacing = 44;//每行的item與item之間最小間隔(如果)layout.minimumInteritemSpacing = 20;//每個(gè)section的頭部大小layout.headerReferenceSize = CGSizeMake(44, 44);//每個(gè)section距離上方和下方20,左方和右方10layout.sectionInset = UIEdgeInsetsMake(20, 10, 20, 10);//垂直滾動(水平滾動設(shè)置UICollectionViewScrollDirectionHorizontal)layout.scrollDirection = UICollectionViewScrollDirectionVertical; }運(yùn)行結(jié)果如下
?
6:修改特定cell大小
包括上面配置header高度時(shí)使用的方法- collectionView:layout:referenceSizeForHeaderInSection:
UICollectionViewDelegateFlowLayout還提供了方法對特定cell大小,間距進(jìn)行設(shè)置
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {if (indexPath.section == 0) {return CGSizeMake(80, 40);} else {return CGSizeMake(40, 40);} }7:設(shè)置delegate,通過delegate中的方法可以設(shè)置cell的點(diǎn)擊事件,這部分和UITableView差不多
?
UICollectionViewFlowLayout的擴(kuò)展
上一部分我們直接使用了UICollectionViewFlowLayout,我們也可以繼承此布局實(shí)現(xiàn)更多的效果,蘋果官方給出了一個(gè)flowlayout的demo,實(shí)現(xiàn)滾動時(shí)item放大以及網(wǎng)格對齊的功能
?
1:新建我們的cell類
//LineLayoutCell.h @interface LineLayoutCell : UICollectionViewCell @property (strong, nonatomic) UILabel* label; @end//LineLayoutCell.m @implementation LineLayoutCell- (id)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {self.label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, frame.size.width, frame.size.height)];self.label.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;self.label.textAlignment = NSTextAlignmentCenter;self.label.font = [UIFont boldSystemFontOfSize:50.0];self.label.backgroundColor = [UIColor underPageBackgroundColor];self.label.textColor = [UIColor blackColor];[self.contentView addSubview:self.label];;self.contentView.layer.borderWidth = 1.0f;self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;}return self; }@end?
2:storyboard中新建UICollectionViewController,設(shè)置類為我們自定義的LineCollectionViewController,并設(shè)置Layout為我們自定義的LineLayout
?
3:在我們自定義的LineCollectionViewController中配置數(shù)據(jù)源
//LineCollectionViewController.h @interface LineCollectionViewController : UICollectionViewController @end//LineCollectionViewController.m @implementation LineCollectionViewController-(void)viewDidLoad {[self.collectionView registerClass:[LineLayoutCell class] forCellWithReuseIdentifier:@"MY_CELL"]; }- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section; {return 60; }- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {LineLayoutCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"MY_CELL" forIndexPath:indexPath];cell.label.text = [NSString stringWithFormat:@"%d",indexPath.item];return cell; } @end?
4:設(shè)置LineLayout?
我們設(shè)置數(shù)據(jù)橫向滾動,item大小為CGSizeMake(200, 200),并設(shè)置每列數(shù)據(jù)上下各間隔200,這樣一行只有一列數(shù)據(jù)
//由于使用了storyboard的關(guān)系,需要使用initWithCoder -(id)initWithCoder:(NSCoder *)aDecoder {self = [super initWithCoder:aDecoder];if (self) {self.itemSize = CGSizeMake(ITEM_SIZE, ITEM_SIZE);self.scrollDirection = UICollectionViewScrollDirectionHorizontal;self.sectionInset = UIEdgeInsetsMake(200, 0.0, 200, 0.0);self.minimumLineSpacing = 50.0;}return self; }?
然后設(shè)置item滾動居中,只需要實(shí)現(xiàn)方法-targetContentOffsetForProposedContentOffset:withScrollingVelocity,此方法第一個(gè)參數(shù)為不加偏移量預(yù)期滾動停止時(shí)的ContentOffset,返回值類型為CGPoint,代表x,y的偏移
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {CGFloat offsetAdjustment = MAXFLOAT;//預(yù)期滾動停止時(shí)水平方向的中心點(diǎn)CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);//預(yù)期滾動停止時(shí)顯示在屏幕上的區(qū)域CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);//獲取該區(qū)域的UICollectionViewLayoutAttributes集合NSArray* array = [super layoutAttributesForElementsInRect:targetRect];for (UICollectionViewLayoutAttributes* layoutAttributes in array) {CGFloat itemHorizontalCenter = layoutAttributes.center.x;//循環(huán)結(jié)束后offsetAdjustment的值就是預(yù)期滾定停止后離水平方向中心點(diǎn)最近的item的中心店if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) {offsetAdjustment = itemHorizontalCenter - horizontalCenter;}}//返回偏移量return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y); }?上面的代碼中出現(xiàn)了一個(gè)新的類?UICollectionViewLayoutAttributes
UICollectionViewLayoutAttributes是一個(gè)非常重要的類,先來看看property列表:
- @property (nonatomic) CGRect frame
- @property (nonatomic) CGPoint center
- @property (nonatomic) CGSize size
- @property (nonatomic) CATransform3D transform3D
- @property (nonatomic) CGFloat alpha
- @property (nonatomic) NSInteger zIndex
- @property (nonatomic, getter=isHidden) BOOL hidden
可以看到,UICollectionViewLayoutAttributes的實(shí)例中包含了諸如邊框,中心點(diǎn),大小,形狀,透明度,層次關(guān)系和是否隱藏等信息。和DataSource的行為十分類似,當(dāng)UICollectionView在獲取布局時(shí)將針對每一個(gè)indexPath的部件(包括cell,追加視圖和裝飾視圖),向其上的UICollectionViewLayout實(shí)例詢問該部件的布局信息(在這個(gè)層面上說的話,實(shí)現(xiàn)一個(gè)UICollectionViewLayout的時(shí)候,其實(shí)很像是zap一個(gè)delegate,之后的例子中會很明顯地看出),這個(gè)布局信息,就以UICollectionViewLayoutAttributes的實(shí)例的方式給出。
?
接下來設(shè)置item滾動過程中放大縮小效果
#define ACTIVE_DISTANCE 200 #define ZOOM_FACTOR 0.3 -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {//獲取rect區(qū)域的UICollectionViewLayoutAttributes集合NSArray* array = [super layoutAttributesForElementsInRect:rect];CGRect visibleRect;visibleRect.origin = self.collectionView.contentOffset;visibleRect.size = self.collectionView.bounds.size;for (UICollectionViewLayoutAttributes* attributes in array) {//只處理可視區(qū)域內(nèi)的itemif (CGRectIntersectsRect(attributes.frame, rect)) {//可視區(qū)域中心點(diǎn)與item中心點(diǎn)距離CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;CGFloat normalizedDistance = distance / ACTIVE_DISTANCE;if (ABS(distance) < ACTIVE_DISTANCE) {//放大系數(shù)//當(dāng)可視區(qū)域中心點(diǎn)和item中心點(diǎn)距離為0時(shí)達(dá)到最大放大倍數(shù)1.3//當(dāng)可視區(qū)域中心點(diǎn)和item中心點(diǎn)距離大于200時(shí)達(dá)到最小放大倍數(shù)1,也就是不放大//距離在0~200之間時(shí)放大倍數(shù)在1.3~1CGFloat zoom = 1 + ZOOM_FACTOR*(1 - ABS(normalizedDistance));attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);attributes.zIndex = 1;}}}return array; }- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)oldBounds {return YES; }?對于個(gè)別UICollectionViewLayoutAttributes進(jìn)行調(diào)整,以達(dá)到滿足設(shè)計(jì)需求是UICollectionView使用中的一種思路。在根據(jù)位置提供不同layout屬性的時(shí)候,需要記得讓-shouldInvalidateLayoutForBoundsChange:返回YES,這樣當(dāng)邊界改變的時(shí)候,-invalidateLayout會自動被發(fā)送,才能讓layout得到刷新。
?
5:運(yùn)行程序查看結(jié)果
?
使用自定義UICollectionViewLayout
?如果我們想實(shí)現(xiàn)更加復(fù)雜的布局,那就必須自定義我們自己的UICollectionView,實(shí)現(xiàn)一個(gè)自定義layout的常規(guī)做法是繼承UICollectionViewLayout類,然后重載下列方法
- -(CGSize)collectionViewContentSize:返回collectionView內(nèi)容的尺寸,
- -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect:返回rect范圍內(nèi)所有元素的屬性數(shù)組,屬性是UICollectionViewLayoutAttributes,通過這個(gè)屬性數(shù)組就能決定每個(gè)元素的布局樣式
UICollectionViewLayoutAttributes可以是cell,追加視圖或裝飾視圖的信息,通過以下三種不同的UICollectionViewLayoutAttributes初始化方法可以得到不同類型的UICollectionViewLayoutAttributes ? ?
?
- - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path:返回對應(yīng)于indexPath的元素的屬性
- -(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath:返回對應(yīng)于indexPath的位置的追加視圖的布局屬性,如果沒有追加視圖可不重載
- -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath:返回對應(yīng)于indexPath的位置的裝飾視圖的布局屬性,如果沒有裝飾視圖可不重載
- -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds:當(dāng)邊界發(fā)生改變時(shí),是否應(yīng)該刷新布局。如果YES則在邊界變化(一般是scroll到其他地方)時(shí),將重新計(jì)算需要的布局信息
?
另外需要了解的是,在初始化一個(gè)UICollectionViewLayout實(shí)例后,會有一系列準(zhǔn)備方法被自動調(diào)用,以保證layout實(shí)例的正確。
首先,-(void)prepareLayout將被調(diào)用,默認(rèn)下該方法什么沒做,但是在自己的子類實(shí)現(xiàn)中,一般在該方法中設(shè)定一些必要的layout的結(jié)構(gòu)和初始需要的參數(shù)等。
之后,-(CGSize) collectionViewContentSize將被調(diào)用,以確定collection應(yīng)該占據(jù)的尺寸。注意這里的尺寸不是指可視部分的尺寸,而應(yīng)該是所有內(nèi)容所占的尺寸。collectionView的本質(zhì)是一個(gè)scrollView,因此需要這個(gè)尺寸來配置滾動行為。
接下來-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被調(diào)用,這個(gè)沒什么值得多說的。初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定。
另外,在需要更新layout時(shí),需要給當(dāng)前l(fā)ayout發(fā)送 -invalidateLayout,該消息會立即返回,并且預(yù)約在下一個(gè)loop的時(shí)候刷新當(dāng)前l(fā)ayout,這一點(diǎn)和UIView的setNeedsLayout方法十分類似。在
-invalidateLayout后的下一個(gè)collectionView的刷新loop中,又會從prepareLayout開始,依次再調(diào)用-collectionViewContentSize和-layoutAttributesForElementsInRect來生成更新后的布局。
?
蘋果官方給出了一個(gè)circlelayout的demo
?1:新建我們的cell類
//CircleLayoutCell.h @interface CircleLayoutCell : UICollectionViewCell @end//CircleLayoutCell.m @implementation CircleLayoutCell - (id)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {self.contentView.layer.cornerRadius = 35.0;self.contentView.layer.borderWidth = 1.0f;self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;self.contentView.backgroundColor = [UIColor underPageBackgroundColor];}return self; } @end?
2:storyboard中新建UICollectionViewController,設(shè)置類為我們自定義的CircleCollectionViewController,并設(shè)置Layout為我們自定義的CircleLayout
?
3:在我們自定義的CircleCollectionViewController中配置數(shù)據(jù)源
//CircleCollectionViewController.h @interface CircleCollectionViewController : UICollectionViewController @end//CircleCollectionViewController.m @interface CircleCollectionViewController () @property (nonatomic, assign) NSInteger cellCount; @end@implementation CircleCollectionViewController - (void)viewDidLoad {[super viewDidLoad];self.cellCount = 20;[self.collectionView registerClass:[CircleLayoutCell class] forCellWithReuseIdentifier:@"MY_CELL"];self.collectionView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor]; }- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section; {return self.cellCount; }- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {CircleLayoutCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"MY_CELL" forIndexPath:indexPath];return cell; }@end??
4:設(shè)置CircleLayout?
首先在prepareLayout中設(shè)置界面圓心的位置以及半徑
-(void)prepareLayout {[super prepareLayout];CGSize size = self.collectionView.frame.size;//當(dāng)前元素的個(gè)數(shù)_cellCount = [[self collectionView] numberOfItemsInSection:0];_center = CGPointMake(size.width / 2.0, size.height / 2.0);_radius = MIN(size.width, size.height) / 2.5; }其實(shí)對于一個(gè)size不變的collectionView來說,除了_cellCount之外的中心和半徑的定義也可以扔到init里去做,但是顯然在prepareLayout里做的話具有更大的靈活性。因?yàn)槊看沃匦陆o出layout時(shí)都會調(diào)用prepareLayout,這樣在以后如果有collectionView大小變化的需求時(shí)也可以自動適應(yīng)變化
?
之后設(shè)置內(nèi)容collectionView內(nèi)容的尺寸,這個(gè)demo中內(nèi)容尺寸就是屏幕可視區(qū)域
-(CGSize)collectionViewContentSize {return [self collectionView].frame.size; }?
接下來在-layoutAttributesForElementsInRect中返回各個(gè)元素屬性組成的屬性數(shù)組
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {NSMutableArray* attributes = [NSMutableArray array];for (NSInteger i=0 ; i < self.cellCount; i++) {NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];}return attributes; }- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path {//初始化一個(gè)UICollectionViewLayoutAttributesUICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];//元素的大小attributes.size = CGSizeMake(70, 70);//元素的中心點(diǎn)attributes.center = CGPointMake(_center.x + _radius * cosf(2 * path.item * M_PI / _cellCount),_center.y + _radius * sinf(2 * path.item * M_PI / _cellCount));return attributes; }?
5:運(yùn)行程序查看結(jié)果
?
添加和刪除數(shù)據(jù)
我們經(jīng)常需要在collectionview中動態(tài)地添加一個(gè)元素或者刪除一個(gè)元素,collectionview提供了下面的函數(shù)處理數(shù)據(jù)的刪除與添加
- -deleteItemsAtIndexPaths:刪除對應(yīng)indexPath處的元素
- -insertItemsAtIndexPaths:在indexPath位置處添加一個(gè)元素
- -performBatchUpdates:completion:這個(gè)方法可以用來對collectionView中的元素進(jìn)行批量的插入,刪除,移動等操作
繼續(xù)上面的CircleLayout的demo,我們?yōu)閏ollectionView添加點(diǎn)擊事件,如果點(diǎn)擊某個(gè)元素則刪除此元素,如果點(diǎn)擊元素外的區(qū)域則在第一個(gè)位置新加一個(gè)元素
//CircleCollectionViewController.m @implementation CircleCollectionViewController - (void)viewDidLoad {[super viewDidLoad];self.cellCount = 20;[self.collectionView registerClass:[CircleLayoutCell class] forCellWithReuseIdentifier:@"MY_CELL"];self.collectionView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];[self.collectionView addGestureRecognizer:tapRecognizer]; }- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section; {return self.cellCount; }- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {CircleLayoutCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"MY_CELL" forIndexPath:indexPath];return cell; }- (void)handleTapGesture:(UITapGestureRecognizer *)sender {if (sender.state == UIGestureRecognizerStateEnded){CGPoint initialPinchPoint = [sender locationInView:self.collectionView];NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:initialPinchPoint];if (tappedCellPath!=nil){self.cellCount = self.cellCount - 1;[self.collectionView performBatchUpdates:^{[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:tappedCellPath]];} completion:nil];}else{self.cellCount = self.cellCount + 1;[self.collectionView performBatchUpdates:^{[self.collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForItem:0 inSection:0]]];} completion:nil];}} }@end有時(shí)候我們希望給刪除和添加元素加點(diǎn)動畫,layout中提供了下列方法處理動畫
- initialLayoutAttributesForAppearingItemAtIndexPath:
- initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
- finalLayoutAttributesForDisappearingItemAtIndexPath:
- finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:
需要注意的是以上4個(gè)方法會對所有顯示的元素調(diào)用,所以我們需要兩個(gè)數(shù)組放置剛添加或者刪除的元素,只對它們進(jìn)行動畫處理,在insert或者delete之前prepareForCollectionViewUpdates:會被調(diào)用,insert或者delete之后finalizeCollectionViewUpdates:會被調(diào)用,我們可以在這兩個(gè)方法中設(shè)置和銷毀我們的數(shù)組
CircleLayout的完整代碼如下
//CircleLayout.m #define ITEM_SIZE 70@interface CircleLayout()// arrays to keep track of insert, delete index paths @property (nonatomic, strong) NSMutableArray *deleteIndexPaths; @property (nonatomic, strong) NSMutableArray *insertIndexPaths;@end@implementation CircleLayout-(void)prepareLayout {[super prepareLayout];CGSize size = self.collectionView.frame.size;//當(dāng)前元素的個(gè)數(shù)_cellCount = [[self collectionView] numberOfItemsInSection:0];_center = CGPointMake(size.width / 2.0, size.height / 2.0);_radius = MIN(size.width, size.height) / 2.5; }-(CGSize)collectionViewContentSize {return [self collectionView].frame.size; }- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path {//初始化一個(gè)UICollectionViewLayoutAttributesUICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];//元素的大小attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);//元素的中心點(diǎn)attributes.center = CGPointMake(_center.x + _radius * cosf(2 * path.item * M_PI / _cellCount),_center.y + _radius * sinf(2 * path.item * M_PI / _cellCount));return attributes; }-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {NSMutableArray* attributes = [NSMutableArray array];for (NSInteger i=0 ; i < self.cellCount; i++) {NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];}return attributes; }- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems {// Keep track of insert and delete index paths [super prepareForCollectionViewUpdates:updateItems];self.deleteIndexPaths = [NSMutableArray array];self.insertIndexPaths = [NSMutableArray array];for (UICollectionViewUpdateItem *update in updateItems){if (update.updateAction == UICollectionUpdateActionDelete){[self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];}else if (update.updateAction == UICollectionUpdateActionInsert){[self.insertIndexPaths addObject:update.indexPathAfterUpdate];}} }- (void)finalizeCollectionViewUpdates {[super finalizeCollectionViewUpdates];// release the insert and delete index pathsself.deleteIndexPaths = nil;self.insertIndexPaths = nil; }// Note: name of method changed // Also this gets called for all visible cells (not just the inserted ones) and // even gets called when deleting cells! - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {// Must call superUICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];if ([self.insertIndexPaths containsObject:itemIndexPath]){// only change attributes on inserted cellsif (!attributes)attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];// Configure attributes ...attributes.alpha = 0.0;attributes.center = CGPointMake(_center.x, _center.y);}return attributes; }// Note: name of method changed // Also this gets called for all visible cells (not just the deleted ones) and // even gets called when inserting cells! - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {// So far, calling super hasn't been strictly necessary here, but leaving it in// for good measureUICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];if ([self.deleteIndexPaths containsObject:itemIndexPath]){// only change attributes on deleted cellsif (!attributes)attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];// Configure attributes ...attributes.alpha = 0.0;attributes.center = CGPointMake(_center.x, _center.y);attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);}return attributes; }@end??
布局切換
UICollectionView最大的好處是數(shù)據(jù)源,交互與布局的獨(dú)立和解耦,我們可以方便地使用一套數(shù)據(jù)在幾種布局中切換,直接更改collectionView的collectionViewLayout屬性可以立即切換布局。而如果通過setCollectionViewLayout:animated:,則可以在切換布局的同時(shí),使用動畫來過渡。對于每一個(gè)cell,都將有對應(yīng)的UIView動畫進(jìn)行對應(yīng)
?
轉(zhuǎn)載于:https://www.cnblogs.com/zanglitao/p/4188526.html
總結(jié)
以上是生活随笔為你收集整理的UICollectionView的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编程之美---点是否在三角形内
- 下一篇: 病毒软件,导致DNS失效的解决办法