UICollectionView的使用

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/UICollectionViewDemooop

1:基本介紹佈局

2:UICollectionViewDataSource和UICollectionViewDelegate介紹動畫

3:使用UICollectionViewFlowLayoutthis

4:UICollectionViewFlowLayout的擴展

5:使用自定義UICollectionViewLayout

6:添加和刪除數據

7:佈局切換

 

基本介紹

UICollectionView是一種新的數據展現方式,簡單來講能夠把他理解成多列的UITableView(請必定注意這是UICollectionView的最最簡單的形式)。若是你用過iBooks的話,可能你還對書架佈局有必定印象:一個虛擬書架上放着你下載和購買的各種圖書,整齊排列。其實這就是一個UICollectionView的表現形式,或者iPad的iOS6中的原生時鐘應用中的各個時鐘,也是UICollectionView的最簡單的一個佈局,如圖:

iOS6 iPad版時鐘應用 最簡單的UICollectionView就是一個GridView,能夠以多列的方式將數據進行展現。標準的UICollectionView包含三個部分,它們都是UIView的子類:

  • Cells 用於展現內容的主體,對於不一樣的cell能夠指定不一樣尺寸和不一樣的內容,這個稍後再說
  • Supplementary Views 追加視圖 若是你對UITableView比較熟悉的話,能夠理解爲每一個Section的Header或者Footer,用來標記每一個section的view
  • Decoration Views 裝飾視圖 這是每一個section的背景,好比iBooks中的書架就是這個

 

無論一個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,或者使用瞭如下方法進行註冊

  • -registerClass:forCellWithReuseIdentifier:
  • -registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
  • -registerNib:forCellWithReuseIdentifier:
  • -registerNib:forSupplementaryViewOfKind:withReuseIdentifier:

那麼能夠更簡單地實現重用

- (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後,會依次執行協議中如下方法

  1. -collectionView:shouldHighlightItemAtIndexPath: 是否應該高亮?
  2. -collectionView:didHighlightItemAtIndexPath: 若是1回答爲是,那麼高亮
  3. -collectionView:shouldSelectItemAtIndexPath: 不管1結果如何,都詢問是否能夠被選中?
  4. -collectionView:didUnhighlightItemAtIndexPath: 若是1回答爲是,那麼如今取消高亮
  5. -collectionView:didSelectItemAtIndexPath: 若是3回答爲是,那麼選中cell

狀態控制要比之前靈活一些,對應的高亮和選中狀態分別由highlighted和selected兩個屬性表示。

關於Cell

相對於UITableViewCell來講,UICollectionViewCell沒有這麼多花頭。首先UICollectionViewCell不存在各式各樣的默認的style,這主要是因爲展現對象的性質決定的,由於UICollectionView所用來展現的對象相比UITableView來講要來得靈活,大部分狀況下更偏向於圖像而非文字,所以需求將會千奇百怪。所以SDK提供給咱們的默認的UICollectionViewCell結構上相對比較簡單,由下至上:

  • 首先是cell自己做爲容器view
  • 而後是一個大小自動適應整個cell的backgroundView,用做cell平時的背景
  • 再其上是selectedBackgroundView,是cell被選中時的背景
  • 最後是一個contentView,自定義內容應被加在這個view上

此次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經常使用的配置屬性以下

  • CGSize itemSize:它定義了每個item的大小。經過設定itemSize能夠全局地改變全部cell的尺寸,若是想要對某個cell制定尺寸,可使用-collectionView:layout:sizeForItemAtIndexPath:方法。
  • CGFloat minimumLineSpacing:每一行的間距
  • CGFloat minimumInteritemSpacing:item與item的間距
  • UIEdgeInsets sectionInset:每一個section的縮進
  • UICollectionViewScrollDirection scrollDirection:設定是垂直流佈局仍是橫向流佈局,默認是UICollectionViewScrollDirectionVertical
  • CGSize headerReferenceSize:設定header尺寸
  • CGSize footerReferenceSize:設定footer尺寸

上面都是全局屬性的設置,咱們能夠經過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列表:

  • @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的實例中包含了諸如邊框,中心點,大小,形狀,透明度,層次關係和是否隱藏等信息。和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類,而後重載下列方法

  • -(CGSize)collectionViewContentSize:返回collectionView內容的尺寸,
  • -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect:返回rect範圍內全部元素的屬性數組,屬性是UICollectionViewLayoutAttributes,經過這個屬性數組就能決定每一個元素的佈局樣式

UICollectionViewLayoutAttributes能夠是cell,追加視圖或裝飾視圖的信息,經過如下三種不一樣的UICollectionViewLayoutAttributes初始化方法能夠獲得不一樣類型的UICollectionViewLayoutAttributes    

  1. layoutAttributesForCellWithIndexPath:
  2. layoutAttributesForSupplementaryViewOfKind:withIndexPath:
  3. layoutAttributesForDecorationViewOfKind:withIndexPath:

 

  • - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path:返回對應於indexPath的元素的屬性
  • -(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath:返回對應於indexPath的位置的追加視圖的佈局屬性,若是沒有追加視圖可不重載
  • -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath:返回對應於indexPath的位置的裝飾視圖的佈局屬性,若是沒有裝飾視圖可不重載
  • -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds:當邊界發生改變時,是否應該刷新佈局。若是YES則在邊界變化(通常是scroll到其餘地方)時,將從新計算須要的佈局信息

 

另外須要瞭解的是,在初始化一個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提供了下面的函數處理數據的刪除與添加

  • -deleteItemsAtIndexPaths:刪除對應indexPath處的元素
  • -insertItemsAtIndexPaths:在indexPath位置處添加一個元素
  • -performBatchUpdates:completion:這個方法能夠用來對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中提供了下列方法處理動畫

  • initialLayoutAttributesForAppearingItemAtIndexPath:
  • initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
  • finalLayoutAttributesForDisappearingItemAtIndexPath:
  • finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:

須要注意的是以上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動畫進行對應

相關文章
相關標籤/搜索