iOS—UICollectionView的简单使用

文章目录

      • UICollectionView与UITableView
      • 实现一个简单的九宫格类布局
      • UICollectionView的代理方法
        • UICollectionViewDataSource协议
        • UICollectionViewDelegate协议
      • 使用FlowLayout进行灵活布局
      • UICollectionViewFlowLayout相关属性方法
      • 动态的配置layout的相关属性UICollectionViewDelegateFlowLayout
      • 自定义FlowLayout进行瀑布流布局
      • 进行自定义瀑布流布局
      • UICollectionViewLayoutAttributes类中我们可以配置的属性
      • 圆环布局的实现

UICollectionView与UITableView

UICollectionView是iOS6之后引入的一个新的UI控件,它和UITableView有着诸多相似之处,很多代理方法都十分类似,但UICollectionView是比UITableView更加强大的控件,有如下几个方面

  • 支持水平和垂直两个方向上的布局
  • 通过layout配置方式进行布局
  • CollectionView中的item大小和位置可以自定义
  • 通过layout布局回调的代理方法,可以动态的定制每一个item的大小和collection的大体布局属性
  • 可以完全自定义一套layout布局方案,实现意想不到的效果

实现一个简单的九宫格类布局

#import "ViewController.h"

@interface ViewController () <UICollectionViewDelegate, UICollectionViewDataSource>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];
    
    //layout布局类
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    //布局方向为垂直流布局
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
    //layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    //设置每个item的大小
    layout.itemSize = CGSizeMake(120, 100);
    _collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
    
    //代理设置
    _collectionView.delegate = self;
    _collectionView.dataSource = self;
    
    //注册item类型,这里使用系统类型
    //必须注册!!!!,注册类似于tableView,但又不同于tableView,因为tableView有不注册的方法,临时创建
    //iOS6之后的新类,统一从cell复用池获取cell,没有提供返回nil的方式
    [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
    [self.view addSubview:_collectionView];
}

//返回分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

//返回每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 30;
}


//返回每个item
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
    //只能用上面这个方法获取,其他方法都会崩溃
    
    //UICollectionViewCell *cell = [[UICollectionViewCell alloc] init];
    cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
    return cell;
    
}

iOS—UICollectionView的简单使用_第1张图片

UICollectionView的代理方法

UICollectionViewDataSource协议

这个协议主要用于collectionView相关数据的处理
首先有两个方法是必须要实现的

  • 设置每个分区的Item个数
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
  • 设置返回每个item的属性
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

下面的方法都是可选实现的

  • 设置分区数,虽然这个方法是可选的,一般我们都会去实现
    - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
  • 对头视图或者尾视图进行设置
    - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
  • 设置某个item是否可以被移动,返回NO则不能移动
    - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
  • 移动item的时候,会调用这个方法
    - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
UICollectionViewDelegate协议

这个协议用来设置和处理collectionView的功能和一些逻辑,所有的方法都是可选实现

  • 是否允许某个Item的高亮,返回NO,则不能进入高亮状态
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
  • 当item高亮时触发的方法
    - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
  • 结束高亮状态时触发的方法
    - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
  • 是否可以选中某个Item,返回NO,则不能选中
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
  • 是否可以取消选中某个Item
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
  • 已经选中某个item时触发的方法
    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
  • 取消选中某个Item时触发的方法
    - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
  • 将要加载某个Item时调用的方法
    - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
  • 将要加载头尾视图时调用的方法
    - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
  • 已经展示某个Item时触发的方法
    - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
  • 已经展示某个头尾视图时触发的方法
    - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
  • collectionView进行重新布局时调用的方法
    - (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;

使用FlowLayout进行灵活布局

布局的管理类UICollectionViewFlowLayout

关于布局的相关设置和属性方法,通过对layout的设置,我们可以编写更加灵活的布局效果

前面的基础九宫格的布局方式中规中矩,有时候并不能满足我们的需求,有时我们需要对每一个item展示不同的大小

在上面那份代码中添加这个方法的实现

//设置每个item的大小,双数的为70*70 单数的为120*120
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    if (indexPath.row%2==0) {
        return CGSizeMake(70, 70);
    }else{
        return CGSizeMake(120, 120);
    }
}

iOS—UICollectionView的简单使用_第2张图片

UICollectionViewFlowLayout相关属性方法

UICollectionViewFlowLayout是系统提供给我们一个封装好的流布局设置类,其中一些布局属性我们可以进行设置

  • 设置行与行之间的间距最小距离
    @property (nonatomic) CGFloat minimumLineSpacing;
  • 设置列与列之间的间距最小距离
    @property (nonatomic) CGFloat minimumInteritemSpacing;
  • 设置每个item的大小
    @property (nonatomic) CGSize itemSize;
  • 设置每个Item的估计大小,一般不需要设置
    @property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);
  • 设置布局方向
    @property (nonatomic) UICollectionViewScrollDirection scrollDirection;
    iOS—UICollectionView的简单使用_第3张图片
  • 设置头视图尺寸大小
    @property (nonatomic) CGSize headerReferenceSize;
  • 设置尾视图尺寸大小
    @property (nonatomic) CGSize footerReferenceSize;
  • 设置分区的EdgeInset
    @property (nonatomic) UIEdgeInsets sectionInset;
    这个属性可以设置分区的偏移量,例如我们在刚才的例子中添加如下设置:四个值分别为上、左、下、右,(这里为了方便看清边界,所以把item的个数改为12个

    layout.sectionInset = UIEdgeInsetsMake(50, 40, 60, 20);
    iOS—UICollectionView的简单使用_第4张图片
  • 下面这两个方法设置分区的头视图和尾视图是否始终固定在屏幕上边和下边
    @property (nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);
    @property (nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);

动态的配置layout的相关属性UICollectionViewDelegateFlowLayout

上面的方法在创建FlowLayout时静态的进行设置,如果我们需要动态的设置这些属性风,就像例子中的每个item大小都有差异,我们就可以通过代理方法来实现

UICollectionViewDelegateFlowLayout是UICollectionViewDelegate的子协议,常用方法如下,这些方法的实现都是可选的

@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>
@optional
//动态设置每个item的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

//动态设置每个分区的EdgeInsets
- (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

自定义FlowLayout进行瀑布流布局

前面对UICollectionView的基础应用和设置UICollectionViewFlowLayout更加灵活的进行布局,但都限制在系统为我们准备好的布局框架中,还是有些局限性

例如要实现瀑布流似得不定高布局,前面的方法就很难满足我们的需求了。这种布局在app中的应用更加广泛,商品的展等都会倾向于采用这种的布局方式

通过自定义FlowLayout,我们也很容易实现

进行自定义瀑布流布局

先新建一个文件继承于UICollectionViewFlowLayout
这里不做更多的封装,只添加一个属性,直接传入一个item个数,我们的重心是在重写布局的方法上

@interface MyLayout : UICollectionViewFlowLayout

@property (nonatomic, assign) int itemCount;

@end

UICollectionViewFlowLayout是一个专门用来管理collectionView布局的类,因此collectionView在进行UI布局前,都会通过这个类获取相关的布局信息。
FlowLayout类将这些布局信息全部存放到了一个数组中,数组中是UICollectionViewLayoutAttributes类,这个类是对item布局的具体设置。

总之,FlowLayout类将每个item的位置等布局信息放在一个数组中,在collectionView布局时,会调用FlowLayout类的layoutAttributesForElementsInRect:方法来获取这个布局配置数组。
因此我们需要重写这个方法,返回我们自定义的配置数组。
另外,FlowLayout类在进行布局之前,会调用prepareLayout方法,所以我们可以重写这个方法,在这里面对我们自定义配置数据进行一些设置。

简单来说,自定义一个FlowLayout布局类就是两个步骤

  • 设计好我们的布局配置数据, perpareLayout方法中
  • 返回配置数组, layoutAttributesForElementsInRect方法中

示例代码:

#import "MyLayout.h"

@implementation MyLayout {
    //自定义的配置数组
    NSMutableArray *_attributeArray;
}

//数组的相关设置在这个方法中
//布局前的准备 会调用这个方法
- (void)prepareLayout {
    _attributeArray = [[NSMutableArray alloc] init];
    [super prepareLayout];
    
    //设置为静态的2列
    //计算每一个item的宽度
    float itemWidth = ([UIScreen mainScreen].bounds.size.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing ) / 2;
    
    //定义数组保存每一列的高度
    //这个数组的主要作用是保存每一列的总高度,这个样在布局时,我们可以始终将下一个item放在最短的列下面
    CGFloat colHeight[2] = {self.sectionInset.top, self.sectionInset.bottom};
    
    //itemCount是外界传进来的item的个数 遍历来设置每一个item的布局
    for (int i = 0; i < _itemCount; i++) {
        //设置每一个item的位置等相关属性
        NSIndexPath *index = [NSIndexPath indexPathForItem:i inSection:0];
        //创建一个布局属性类, 通过indexPath来创建
        UICollectionViewLayoutAttributes *attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index];
        //随意一个高度 在60-260之间
        CGFloat height = arc4random() % 200 + 60;
        
        //哪一行高度小 则放到哪一列下面
        //标记最短的列
        int flag = 0;
        if (colHeight[0] < colHeight[1]) {
            //将新的item高度加入到短的一列
            colHeight[0] = colHeight[0] + height + self.minimumLineSpacing;
            flag = 0;
        } else {
            colHeight[1] = colHeight[1] + height + self.minimumLineSpacing;
            flag = 1;
        }
        
        //设置item的位置
        attris.frame = CGRectMake(self.sectionInset.left + (self.minimumInteritemSpacing + itemWidth) * flag, colHeight[flag] - height - self.minimumLineSpacing, itemWidth, height);
        
        [_attributeArray addObject:attris];
    }
    
    //设置itemSize来确保滑动范围的正确 这里是通过将所有的item高度平均化,计算出来的 (以最高的列为标准)
    if (colHeight[0] > colHeight[1]) {
        self.itemSize = CGSizeMake(itemWidth, (colHeight[0] - self.sectionInset.top) * 2 / _itemCount - self.minimumLineSpacing);
    } else {
        self.itemSize = CGSizeMake(itemWidth, (colHeight[0] - self.sectionInset.top) * 2 / _itemCount - self.minimumLineSpacing);
    }
    
}

//返回布局数组
- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return _attributeArray;
}

@end



//
#import "ViewController.h"
#import "MyLayout.h"

@interface ViewController () < UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];

    MyLayout *layout = [[MyLayout alloc] init];
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
    layout.itemCount = 30;
    
    UICollectionView *collection = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
   
    
    collection.delegate = self;
    collection.dataSource = self;
    
    [collection registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
    [self.view addSubview:collection];
}

//返回分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

//返回每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 30;
}


//返回每个item
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {


    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
  
    cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
    return cell;

}

iOS—UICollectionView的简单使用_第5张图片

UICollectionViewLayoutAttributes类中我们可以配置的属性

//配置item的布局位置
@property (nonatomic) CGRect frame;
//配置item的中心
@property (nonatomic) CGPoint center;
//配置item的尺寸
@property (nonatomic) CGSize size;
//配置item的3D效果
@property (nonatomic) CATransform3D transform3D;
//配置item的bounds
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
//配置item的旋转
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
//配置item的alpha
@property (nonatomic) CGFloat alpha;
//配置item的z坐标
@property (nonatomic) NSInteger zIndex; // default is 0
//配置item的隐藏
@property (nonatomic, getter=isHidden) BOOL hidden; 
//item的indexpath
@property (nonatomic, strong) NSIndexPath *indexPath;
//获取item的类型
@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; 
 
//一些创建方法
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;


通过上面的属性,可以布局出各式各样的炫酷效果

圆环布局的实现

前面的不定高瀑布流布局中,通过设置具体的布局属性类UICollectionViewLayoutAttributes来设置每一个item的具体位置

如果位置我们可以自由控制,那么布局也就更加灵活

先定义一个layout类,继承于UICollectionViewLayout,这个类是一个布局抽象基类,我们要使用自定义的布局方式,必须将其子类化。

在瀑布流布局时就使用过UICollectionViewFlowLayout类,这个类就是继承于UICollectionViewLayout类,是系统为我们实现好的一个布局方案

//设计一个圆环布局
@interface CircleLayout : UICollectionViewLayout

@property(nonatomic,assign)int itemCount;

@end

//
#import "CircleLayout.h"

@implementation CircleLayout {
    NSMutableArray *_attributeArray;
}

- (void)prepareLayout {
    
    [super prepareLayout];
    
    //获取item个数
    _itemCount = (int)[self.collectionView numberOfItemsInSection:0];
    _attributeArray = [[NSMutableArray alloc] init];
    
    //先设定大圆的半径 取长和宽最短的
    CGFloat radius = MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height) / 2;
    
    //计算圆心的位置
    CGPoint center = CGPointMake(self.collectionView.frame.size.width / 2, self.collectionView.frame.size.height / 2);
    
    //设置每个item的大小为50 * 50,半径为25
    for (int i = 0; i < _itemCount; i++) {
        UICollectionViewLayoutAttributes *attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        
        //设置item大小
        attris.size = CGSizeMake(50, 50);
        
        //计算每个item的圆心位置
        float x = center.x + cosf(2 * M_PI / _itemCount * i) * (radius - 25);
        float y = center.y + sinf(2 * M_PI / _itemCount * i) * (radius - 25);
    
        attris.center = CGPointMake(x, y);
        [_attributeArray addObject:attris];
    }
}

//设置内容区域大小
- (CGSize)collectionViewContentSize {
    return self.collectionView.frame.size;
}

//返回设置数组
- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return _attributeArray;
}


@end


//----------------------------------------------------------------------
//ViewController.m
#import "ViewController.h"
#import "MyLayout.h"
#import "CircleLayout.h"

@interface ViewController () < UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];

    CircleLayout *layout = [[CircleLayout alloc] init];
    UICollectionView *collection = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
    collection.delegate = self;
    collection.dataSource = self;
    
    [collection registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
    [self.view addSubview:collection];
    
    
    
    
    
}

//返回分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

//返回每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 20;
}


//返回每个item
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {


    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
    //只能用上面这个方法获取,其他方法都会崩溃

    //UICollectionViewCell *cell = [[UICollectionViewCell alloc] init];
    cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
    cell.layer.cornerRadius = cell.frame.size.width / 2;
    cell.layer.masksToBounds = YES;
    
    return cell;

}

iOS—UICollectionView的简单使用_第6张图片

参考

你可能感兴趣的:(iOS)