iOS 之UICollectionView 开发步骤 之 OC

  说起来容易做起来难。

 

  那么我就不说了,来做吧。这就是我的style。

  鉴于现在的主流还是OC,那么示例程序还用OC来写,后续补写Swift程序,这里先占个坑

 

  废话不多说,下面开发步骤来了:

1. 创建程序

  万事开头难,先创建一个程序吧,我写完这句话就去创建。取名就叫testCollectionView,但是我要上传到github上,所以,这个名字可能会重复。那么重新弄个工程吧,取名字叫做TestCollectionViewWood。哦,对了,是否只要自己的工程里面没有重复的就可以了。先试一下。

  上传到github,参考:iOS 使用 github

  果然,不需要考虑跟其它用户的程序重名的问题。

2. 创建Collection View

2.1. 创建过程

  在Storyboard里面拖入一个Collection View,然后我调整了大小。实际上,应该不用调整,直接设置Constraints,然后Update Frames即可。我先设置下该Collection View的背景色,方便调试。纯色让我有点恶心,要是什么时候提供图片背景就好了,maybe。

    初始程序下载地址

 

  设置名称为:collectionView

2.2. 设置dataSource和delegate

  设置完名称,顺便写上下面的代码吧,我看到示例程序里没写可以正常运行,但是我没写就是不行(不知道为什么),折腾了半天:

self.collectionView.dataSource = self;
self.collectionView.delegate = self;

   

  无意之间,我找到了原因,可以不写上面这段代码。我是通过搜索dataSource这个关键词找到的。那么下图所示:

  右击Collection View 控件,在弹出的对话框中,分别拖动红圈里的两个圆圈,到右侧的视图位置即可。这样,就可以代替上面的代码。

    需要注意的是,这种方式可能会导致设置item的size无效。所以,还是不要用的好。

3. 显示内容

  • 建立Cell模板,并注册模板
  • 设置Collection View的属性

3.1. 注册模板

   为上面创建的 collectionView 注册可用的Cell模板。输入“[self.collectionView register” 根据提示选择即可。

[self.collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:@"Image_Cell"];

  

  要注意的是,上面的代码只适用于没有xib的情况,我用到xib,则需要下面这样:

[self.collectionView registerNib:[UINib nibWithNibName:@"MyImageCell" bundle:nil] forCellWithReuseIdentifier:@"ImageCell"];

  前面是类名,后面的是xib里的Identifier。

3.2. 必须的三个 Collection View 协议

  • 设置section的数目
  • 设置Item的数目
  • 设置内容协议
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 0;
}

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 0;
}

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    return NULL;
}

  这三个协议是必须的。设置的时候,不用记住名称,也不用拷贝。第一个协议可以输入 “-num” 后根据提示选择,第二、第三个协议输入 “-collection” 后根据选择。

  注意:第一个Section的个数其实是有默认值的,默认是1,如果自己也是只有一个section,则不用设置这个协议了。

3.3. 设置内容

  现在设置内容非常简单,直接获取一个cell,然后设置内容即可。

MyCell *cell =[cv dequeueReusableCellWithReuseIdentifier:@”Image_Cell”];

cell.imageView.image= ...

return cell;

 

  显示内容下载地址

3.4. 优化界面之每行显示两个

  可以看到,上图中的显示效果是完全没有美感的。所以呢,要优化一下,每行的个数为两个。

  如果每行为两个的话,那个每个的大小是多少呢?整个宽度/2??,如果是这样,那就错了,应该算上minimumInteritemSpacing。每行两个,那么,就有一个minimumInteritemSpacing。如果每行有三个Item,那么就有两个minimumInterItemSpacing,依次类推。

  根据上述原理,宽度紧凑地设置为:

CGFloat width = ([[UIScreen mainScreen] bounds].size.width - self.flowLayout.minimumInteritemSpacing)/2;

   效果如下:

  看着行距比间距宽,那是视觉上的错误。其实都是10像素。

  优化界面之一行两个下载地址

3.5. 优化界面之高度不同

  有时,图片会有不同的分辨率,那么,高度就有可能不同才会好看。  

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    CGFloat width = ([[UIScreen mainScreen] bounds].size.width - self.flowLayout.minimumInteritemSpacing)/2;
    CGFloat height = 100;
    switch (indexPath.row) {
        case 0:
            height = 140;
            break;
        case 1:
            height = 240;
            break;
        case 2:
            height = 150;
            break;
        case 3:
            height = 120;
            break;
        default:
            break;
    }
    return CGSizeMake(width, height);
}

  

效果如下:

  实现不同高度下载地址 

3.5. 实现间隙相同

  上面的效果,虽然实现了不同高度,但是导致上下的间隙不均匀,非常难看,那该怎么办?是否需要做几个CollectionView进行嵌套,还是有更还的办法?

  当然有了,要不然还会算什么瀑布流。那么怎么实现呢?实现的话需要要自定义UICollectionViewFlowLayout,然后在里面重新设置cell的位置。大体思路就是这样。

  现在基本上瀑布流的就是分成n列,但是每列的排列都是紧凑的。基本上,如果没有特殊要求,这n列都是等分的。也就是说,需要确定一个值,总共有几列。确定了这个列数后,就相当于知道了cell的宽度。如果是显示图片的话,宽高比要保持不变。已经有宽度了,并且可以得到UIImage的宽高比,那么就能算出同比例的高度来了。

  cell的宽度和高度都知道了,就要确定起始点的坐标了。起始点的坐标x轴无非就每个列的起始位置,当然要出去边框和cell间距的值,就不用细说了。那么Y轴的值怎么弄?Y轴的值要取所有列中Y值最小的那个,然后把新的cell放该列下面。

  既然已经知道实现原理了,那么,实现步骤是什么?

3.5.1. 创建UICollectionViewFlowLayout子类

3.5.2. 创建计算cell高度的协议

  协议里面只有一个必须的接口,返回一个高度值。  参考: iOS 协议

@class WaterFallFlowLayoutGS;

@protocol WaterFallFlowLayoutGSDelegate <NSObject>

//cell 高度
-(CGFloat)getHeightWithWaterFallFlowLayout:(WaterFallFlowLayoutGS*)flowLayout width:(CGFloat)width indexPath:(NSIndexPath*)indexPath;

@end

 

  头文件声明:

@interface WaterFallFlowLayoutGS : UICollectionViewFlowLayout

-(instancetype)initWithColumCount:(int)count;

@property (nonatomic, weak) id<WaterFallFlowLayoutGSDelegate> delegate;

@end

  协议在这里定义、协议在这么声明为属性、协议在这里引用。

3.5.3. 自定义初始化函数

  指定列数。

@interface WaterFallFlowLayoutGS(){
    
    int columnCount;//列数
}

//每一列的当前高度的最大值
@property (nonatomic, strong) NSMutableDictionary *maxHeights;

//所有布局属性
@property (nonatomic, strong) NSMutableArray *attributesArray;

@end

@implementation WaterFallFlowLayoutGS

-(instancetype) initWithColumCount:(int)count{
    
    if (self = [super init]) {
        columnCount = count;
    }
    return self;
}

@end

 

  当然,初始化时可以设置一些影响间距的属性:

self.sectionInset = UIEdgeInsetsMake(5.0, 5.0, 5.0, 5.0);
self.minimumLineSpacing = 5.0;
self.minimumInteritemSpacing = 5.0;

  我用它们在设置间距效果。

3.5.4. 懒加载变量

-(NSMutableDictionary *)maxHeights{
    if (!_maxHeights) {
        _maxHeights = [NSMutableDictionary dictionary];
        for (int i = 0; i < columnCount; i++) {
            NSString *column = [NSString stringWithFormat:@"%d",i];
            self.maxHeights[column] = @"0";
        }
    }
    return _maxHeights;
}

-(NSMutableArray *)attributesArray{
    if (!_attributesArray) {
        _attributesArray = [NSMutableArray array];
    }
    return _attributesArray;
}

 

3.5.5. 设置Layout准备--初始化所有列属性

#pragma mark - 初始化所有属性值
-(void)prepareLayout{
    
    //初始化每列的Y坐标,即在最顶端
    for (int i = 0; i < columnCount; i++) {
        NSString *column = [NSString stringWithFormat:@"%d",i];
        self.maxHeights[column] = @(self.sectionInset.top);
    }
    [self.attributesArray removeAllObjects];
    
    //1.查看共有多少个元素
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    //2.遍历每个item属性
    for (int i = 0; i < count; i++) {
        
        //3.取出布局属性
        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        //4.添加到数组中
        [self.attributesArray addObject:attr];
    }
    
}

 

3.5.6. 设置CollectionView的ContentSize

#pragma mark - 设置整个collectionView的ContentSize
-(CGSize)collectionViewContentSize{
    __block NSString *maxColumn = @"0";
    [self.maxHeights enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL * stop) {
        if ([maxY floatValue] > [self.maxHeights[maxColumn] floatValue]) {
            maxColumn = column;
        }
    }];
    
    return CGSizeMake(0, [self.maxHeights[maxColumn] floatValue] + self.sectionInset.bottom);
    
}

 

3.5.7. 设置所有cell的属性

#pragma mark - 设置所有cell的大小及位置
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    
    return self.attributesArray;
}

 

3.5.8. 计算每个cell的属性

#pragma mark - 每列的大小、位置等属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    //每列中都可能已经有多个元素,找出所有列中 Y坐标 的最大值中的最小值
    __block NSString *miniColumn = @"0";
    [self.maxHeights enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL * stop) {
        if ([maxY floatValue] < [self.maxHeights[miniColumn] floatValue]) {
            miniColumn = column;
        }
        
    }];
    
    
    //计算frame
    CGFloat width = (CGRectGetWidth(self.collectionView.frame) - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * (columnCount - 1))/columnCount;
    CGFloat height = [self.delegate getHeightWithWaterFallFlowLayout:self width:width indexPath:indexPath];
    CGFloat x = self.sectionInset.left + (width + self.minimumInteritemSpacing)*[miniColumn intValue];
    CGFloat y = [self.maxHeights[miniColumn] floatValue] + self.minimumInteritemSpacing;
    self.maxHeights[miniColumn] = @(height + y);
    
    UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attr.frame = CGRectMake(x, y, width, height);
    
    
    return attr;
}

 

3.5.9. 使用自定义的FlowLayout

//
//  ViewController.m
//  testCollectionView
//
//  Created by WoodGao on 15/12/2.
//  Copyright  2015年 wood. All rights reserved.
//

#import "ViewController.h"
#import "MyImageCell.h"
#import "WaterFallFlowLayoutGS.h"

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

@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;

@property (strong, nonatomic) WaterFallFlowLayoutGS *waterFlow;
@property (strong, nonatomic) NSMutableArray *imgArr;

@end

@implementation ViewController

-(void) initImageArray {
    if (nil == _imgArr) {
        NSMutableArray *tempArr = [[NSMutableArray alloc] init];
        for (int i=1; i<=15; i++) {
            UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i]];
            [tempArr addObject:img];
        }
        
        _imgArr = tempArr;
    }
}

//根据宽度,计算高度
-(CGFloat) heightForImage:(UIImage*)image width:(CGFloat)width {
    return image.size.height/image.size.width * width;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _waterFlow = [[WaterFallFlowLayoutGS alloc]initWithColumCount:2];
    _waterFlow.delegate = self;
    self.collectionView.collectionViewLayout = _waterFlow;
    
    [self.collectionView registerNib:[UINib nibWithNibName:@"MyImageCell" bundle:nil] forCellWithReuseIdentifier:@"MyImageCell"];
    
    self.collectionView.dataSource = self;
    self.collectionView.delegate   = self;
    
    [self initImageArray];

}

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return _imgArr.count;
}

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    MyImageCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"MyImageCell" forIndexPath:indexPath];
    cell.image.image = _imgArr[indexPath.item];
    
    return cell;
}

-(CGFloat)getHeightWithWaterFallFlowLayout:(WaterFallFlowLayoutGS *)flowLayout width:(CGFloat)width indexPath:(NSIndexPath *)indexPath{
    CGFloat height = [self heightForImage:_imgArr[indexPath.item] width:width];
    return height;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"当前选中的item 是 %ld",(long)indexPath.row);
    
}

@end

 

效果图如下:

代码下载地址

3.6. 网络下载图片

 

3.x. 点击图片时,最大化该图,并实现动画  

3.x. 移动cell

3.x. 其实可以实现部分列的布局

  思路是这样:在创建的时候定义每个cell的类型,类型不同,则表现形式就不同。不同的类型在被点击的时候,也可以表现出不同的效果。

 

posted on 2015-12-02 17:43  大木哥  阅读(1989)  评论(0编辑  收藏  举报

导航