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动画进行对应

相关文章
相关标签/搜索