UITableView中咱們使用datasource和delegate分別處理咱們的數據和交互,並且UITableView默認提供了兩種樣式供咱們選擇如何呈現數據,在IOS6中蘋果提供了UICollectionView用來更自由地定製呈現咱們的數據。git
UICollectionView使用包括三個部分:github
1.設置數據(使用UICollectionViewDataSource)數組
2.設置數據呈現方式(使用UICollectionViewLayout)app
3.設置界面交互(使用UICollectionViewDelegate)ide
其中1,3和UITableView一致,可見UICollectionView比UITableView更具備通常性(咱們可使用UICollectionView實現UITableView的效果)函數
本篇博客的outline以下(本文參考http://www.onevcat.com/2012/06/introducing-collection-views/,代碼下載地址爲https://github.com/zanglitao/UICollectionViewDemo)oop
1:基本介紹佈局
2:UICollectionViewDataSource和UICollectionViewDelegate介紹動畫
3:使用UICollectionViewFlowLayoutthis
4:UICollectionViewFlowLayout的擴展
5:使用自定義UICollectionViewLayout
6:添加和刪除數據
7:佈局切換
基本介紹
UICollectionView是一種新的數據展現方式,簡單來講能夠把他理解成多列的UITableView(請必定注意這是UICollectionView的最最簡單的形式)。若是你用過iBooks的話,可能你還對書架佈局有必定印象:一個虛擬書架上放着你下載和購買的各種圖書,整齊排列。其實這就是一個UICollectionView的表現形式,或者iPad的iOS6中的原生時鐘應用中的各個時鐘,也是UICollectionView的最簡單的一個佈局,如圖:
最簡單的UICollectionView就是一個GridView,能夠以多列的方式將數據進行展現。標準的UICollectionView包含三個部分,它們都是UIView的子類:
無論一個UICollectionView的佈局如何變化,這三個部件都是存在的。再次說明,複雜的UICollectionView毫不止上面的幾幅圖。
UICollectionViewDataSource和UICollectionViewDelegate介紹
UICollectionViewDataSource用來設置數據,此協議包含的方法以下
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section; //設置每一個section包含的item數目 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; //返回對應indexPath的cell - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView; //返回section的數目,此方法可選,默認返回1 - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; //返回Supplementary Views,此方法可選
對於Decoration Views,提供方法並不在UICollectionViewDataSource中,而是直接UICollectionViewLayout類中的(由於它僅僅是視圖相關,而與數據無關),放到稍後再說。
與UITableViewCell類似的是UICollectionViewCell也支持重用,典型的UITbleViewCell重用寫法以下
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MY_CELL_ID"]; if (!cell) { //若是沒有可重用的cell,那麼生成一個 cell = [[UITableViewCell alloc] init]; } //配置cell,blablabla return cell
UICollectionViewCell重用寫法於UITableViewCell一致,可是如今更簡便的是若是咱們直接在storyboard中對cell設置了identifier,或者使用瞭如下方法進行註冊
那麼能夠更簡單地實現重用
- (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個語句分別提供了nib和class方法對collectionViewCell和supplementaryView進行註冊
UICollectionViewDelegate處理交互,包括cell點擊事件,cell點擊後高亮效果以及長按菜單等設置,當用戶點擊cell後,會依次執行協議中如下方法
狀態控制要比之前靈活一些,對應的高亮和選中狀態分別由highlighted和selected兩個屬性表示。
相對於UITableViewCell來講,UICollectionViewCell沒有這麼多花頭。首先UICollectionViewCell不存在各式各樣的默認的style,這主要是因爲展現對象的性質決定的,由於UICollectionView所用來展現的對象相比UITableView來講要來得靈活,大部分狀況下更偏向於圖像而非文字,所以需求將會千奇百怪。所以SDK提供給咱們的默認的UICollectionViewCell結構上相對比較簡單,由下至上:
此次Apple給咱們帶來的好康是被選中cell的自動變化,全部的cell中的子view,也包括contentView中的子view,在當cell被選中時,會自動去查找view是否有被選中狀態下的改變。好比在contentView里加了一個normal和selected指定了不一樣圖片的imageView,那麼選中這個cell的同時這張圖片也會從normal變成selected,而不須要額外的任何代碼。
使用UICollectionViewFlowLayout
UICollectionViewLayout用來處理數據的佈局,經過它咱們能夠設置每一個cell,Supplementary View以及Decoration Views的呈現方式,好比位置,大小,透明度,形狀等等屬性
Layout決定了UICollectionView是如何顯示在界面上的。在展現以前,通常須要生成合適的UICollectionViewLayout子類對象,並將其賦予CollectionView的collectionViewLayout屬性,蘋果還提供了一個現成的UICollectionViewFlowLayout,經過這個layout咱們能夠很簡單地實現流佈局,UICollectionViewFlowLayout經常使用的配置屬性以下
上面都是全局屬性的設置,咱們能夠經過delegate中的方法對進行定製,經過實現如下這些方法設定的屬性的優先級比全局設定的要高
@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完成一個簡單demo
1:設置咱們的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; } @end
2:設置追加視圖
//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,也能夠設置爲UICollectionElementKindSectionFooter [self.collectionView registerClass:[SimpleFlowLayoutSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_SUPPLEMENT"]; [self.view addSubview:self.collectionView]; }
4:配置datasource
//每一個section中有32個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; }
此時運行程序能夠看到以下界面
程序並無顯示咱們設置的header視圖,這是由於咱們使用的是UICollectionViewFlowLayout默認配置,當前header視圖高度爲0,咱們能夠經過設置UICollectionViewFlowLayout的
headerReferenceSize屬性改變大小,也能夠經過協議方法返回特定section的header大小,這裏咱們先使用後者
咱們添加如下方法
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { return CGSizeMake(44, 44); }
此時再運行就能獲得如下結果
5:配置layout
上面的代碼使用了flowlayout默認的配置,包括itemsize,行間距,item間距,追加視圖大小等等都是默認值,咱們能夠改變這些值
- (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,也能夠設置爲UICollectionElementKindSectionFooter [self.collectionView registerClass:[SimpleFlowLayoutSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_SUPPLEMENT"]; [self.view addSubview:self.collectionView]; //配置UICollectionViewFlowLayout屬性 //每一個itemsize的大小 layout.itemSize = CGSizeMake(80, 50); //行與行的最小間距 layout.minimumLineSpacing = 44; //每行的item與item之間最小間隔(若是) layout.minimumInteritemSpacing = 20; //每一個section的頭部大小 layout.headerReferenceSize = CGSizeMake(44, 44); //每一個section距離上方和下方20,左方和右方10 layout.sectionInset = UIEdgeInsetsMake(20, 10, 20, 10); //垂直滾動(水平滾動設置UICollectionViewScrollDirectionHorizontal) layout.scrollDirection = UICollectionViewScrollDirectionVertical; }
運行結果以下
6:修改特定cell大小
包括上面配置header高度時使用的方法- collectionView:layout:referenceSizeForHeaderInSection:
UICollectionViewDelegateFlowLayout還提供了方法對特定cell大小,間距進行設置
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { return CGSizeMake(80, 40); } else { return CGSizeMake(40, 40); } }
7:設置delegate,經過delegate中的方法能夠設置cell的點擊事件,這部分和UITableView差很少
UICollectionViewFlowLayout的擴展
上一部分咱們直接使用了UICollectionViewFlowLayout,咱們也能夠繼承此佈局實現更多的效果,蘋果官方給出了一個flowlayout的demo,實現滾動時item放大以及網格對齊的功能
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,設置類爲咱們自定義的LineCollectionViewController,並設置Layout爲咱們自定義的LineLayout
3:在咱們自定義的LineCollectionViewController中配置數據源
//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:設置LineLayout
咱們設置數據橫向滾動,item大小爲CGSizeMake(200, 200),並設置每列數據上下各間隔200,這樣一行只有一列數據
//因爲使用了storyboard的關係,須要使用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; }
而後設置item滾動居中,只須要實現方法-targetContentOffsetForProposedContentOffset:withScrollingVelocity,此方法第一個參數爲不加偏移量預期滾動中止時的ContentOffset,返回值類型爲CGPoint,表明x,y的偏移
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { CGFloat offsetAdjustment = MAXFLOAT; //預期滾動中止時水平方向的中心點 CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0); //預期滾動中止時顯示在屏幕上的區域 CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); //獲取該區域的UICollectionViewLayoutAttributes集合 NSArray* array = [super layoutAttributesForElementsInRect:targetRect]; for (UICollectionViewLayoutAttributes* layoutAttributes in array) { CGFloat itemHorizontalCenter = layoutAttributes.center.x; //循環結束後offsetAdjustment的值就是預期滾定中止後離水平方向中心點最近的item的中心店 if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) { offsetAdjustment = itemHorizontalCenter - horizontalCenter; } } //返回偏移量 return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y); }
上面的代碼中出現了一個新的類 UICollectionViewLayoutAttributes
UICollectionViewLayoutAttributes是一個很是重要的類,先來看看property列表:
能夠看到,UICollectionViewLayoutAttributes的實例中包含了諸如邊框,中心點,大小,形狀,透明度,層次關係和是否隱藏等信息。和DataSource的行爲十分相似,當UICollectionView在獲取佈局時將針對每個indexPath的部件(包括cell,追加視圖和裝飾視圖),向其上的UICollectionViewLayout實例詢問該部件的佈局信息(在這個層面上說的話,實現一個UICollectionViewLayout的時候,其實很像是zap一個delegate,以後的例子中會很明顯地看出),這個佈局信息,就以UICollectionViewLayoutAttributes的實例的方式給出。
接下來設置item滾動過程當中放大縮小效果
#define ACTIVE_DISTANCE 200 #define ZOOM_FACTOR 0.3 -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect { //獲取rect區域的UICollectionViewLayoutAttributes集合 NSArray* array = [super layoutAttributesForElementsInRect:rect]; CGRect visibleRect; visibleRect.origin = self.collectionView.contentOffset; visibleRect.size = self.collectionView.bounds.size; for (UICollectionViewLayoutAttributes* attributes in array) { //只處理可視區域內的item if (CGRectIntersectsRect(attributes.frame, rect)) { //可視區域中心點與item中心點距離 CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x; CGFloat normalizedDistance = distance / ACTIVE_DISTANCE; if (ABS(distance) < ACTIVE_DISTANCE) { //放大係數 //當可視區域中心點和item中心點距離爲0時達到最大放大倍數1.3 //當可視區域中心點和item中心點距離大於200時達到最小放大倍數1,也就是不放大 //距離在0~200之間時放大倍數在1.3~1 CGFloat 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; }
對於個別UICollectionViewLayoutAttributes進行調整,以達到知足設計需求是UICollectionView使用中的一種思路。在根據位置提供不一樣layout屬性的時候,須要記得讓-shouldInvalidateLayoutForBoundsChange:返回YES,這樣當邊界改變的時候,-invalidateLayout會自動被髮送,才能讓layout獲得刷新。
5:運行程序查看結果
使用自定義UICollectionViewLayout
若是咱們想實現更加複雜的佈局,那就必須自定義咱們本身的UICollectionView,實現一個自定義layout的常規作法是繼承UICollectionViewLayout類,而後重載下列方法
UICollectionViewLayoutAttributes能夠是cell,追加視圖或裝飾視圖的信息,經過如下三種不一樣的UICollectionViewLayoutAttributes初始化方法能夠獲得不一樣類型的UICollectionViewLayoutAttributes
另外須要瞭解的是,在初始化一個UICollectionViewLayout實例後,會有一系列準備方法被自動調用,以保證layout實例的正確。
首先,-(void)prepareLayout將被調用,默認下該方法什麼沒作,可是在本身的子類實現中,通常在該方法中設定一些必要的layout的結構和初始須要的參數等。
以後,-(CGSize) collectionViewContentSize將被調用,以肯定collection應該佔據的尺寸。注意這裏的尺寸不是指可視部分的尺寸,而應該是全部內容所佔的尺寸。collectionView的本質是一個scrollView,所以須要這個尺寸來配置滾動行爲。
接下來-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被調用,這個沒什麼值得多說的。初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定。
另外,在須要更新layout時,須要給當前layout發送 -invalidateLayout,該消息會當即返回,而且預定在下一個loop的時候刷新當前layout,這一點和UIView的setNeedsLayout方法十分相似。在
-invalidateLayout後的下一個collectionView的刷新loop中,又會從prepareLayout開始,依次再調用-collectionViewContentSize和-layoutAttributesForElementsInRect來生成更新後的佈局。
蘋果官方給出了一個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,設置類爲咱們自定義的CircleCollectionViewController,並設置Layout爲咱們自定義的CircleLayout
3:在咱們自定義的CircleCollectionViewController中配置數據源
//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:設置CircleLayout
首先在prepareLayout中設置界面圓心的位置以及半徑
-(void)prepareLayout { [super prepareLayout]; CGSize size = self.collectionView.frame.size; //當前元素的個數 _cellCount = [[self collectionView] numberOfItemsInSection:0]; _center = CGPointMake(size.width / 2.0, size.height / 2.0); _radius = MIN(size.width, size.height) / 2.5; }
其實對於一個size不變的collectionView來講,除了_cellCount以外的中心和半徑的定義也能夠扔到init裏去作,可是顯然在prepareLayout裏作的話具備更大的靈活性。由於每次從新給出layout時都會調用prepareLayout,這樣在之後若是有collectionView大小變化的需求時也能夠自動適應變化
以後設置內容collectionView內容的尺寸,這個demo中內容尺寸就是屏幕可視區域
-(CGSize)collectionViewContentSize { return [self collectionView].frame.size; }
接下來在-layoutAttributesForElementsInRect中返回各個元素屬性組成的屬性數組
-(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 { //初始化一個UICollectionViewLayoutAttributes UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path]; //元素的大小 attributes.size = CGSizeMake(70, 70); //元素的中心點 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:運行程序查看結果
添加和刪除數據
咱們常常須要在collectionview中動態地添加一個元素或者刪除一個元素,collectionview提供了下面的函數處理數據的刪除與添加
繼續上面的CircleLayout的demo,咱們爲collectionView添加點擊事件,若是點擊某個元素則刪除此元素,若是點擊元素外的區域則在第一個位置新加一個元素
//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
有時候咱們但願給刪除和添加元素加點動畫,layout中提供了下列方法處理動畫
須要注意的是以上4個方法會對全部顯示的元素調用,因此咱們須要兩個數組放置剛添加或者刪除的元素,只對它們進行動畫處理,在insert或者delete以前prepareForCollectionViewUpdates:會被調用,insert或者delete以後finalizeCollectionViewUpdates:會被調用,咱們能夠在這兩個方法中設置和銷燬咱們的數組
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; //當前元素的個數 _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 { //初始化一個UICollectionViewLayoutAttributes UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path]; //元素的大小 attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE); //元素的中心點 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 paths self.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 super UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; if ([self.insertIndexPaths containsObject:itemIndexPath]) { // only change attributes on inserted cells if (!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 measure UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; if ([self.deleteIndexPaths containsObject:itemIndexPath]) { // only change attributes on deleted cells if (!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最大的好處是數據源,交互與佈局的獨立和解耦,咱們能夠方便地使用一套數據在幾種佈局中切換,直接更改collectionView的collectionViewLayout屬性能夠當即切換佈局。而若是經過setCollectionViewLayout:animated:,則能夠在切換佈局的同時,使用動畫來過渡。對於每個cell,都將有對應的UIView動畫進行對應