• 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 (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 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 (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 (CGFloat)width indexPath:(NSIndexPath *)indexPath{
        CGFloat height = [self heightForImage:_imgArr[indexPath.item] 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的类型,类型不同,则表现形式就不同。不同的类型在被点击的时候,也可以表现出不同的效果。

  • 相关阅读:
    git如何从远端获取某个文件
    git显示不出来图标标志
    sublime text3设置
    怎么解决sublime的插件自动被禁用
    外甥语录
    sublime Text3支持vue高亮,sublime Text3格式化Vue
    sass安装方法,绝对好用的方式
    win10 安装nodejs,报错there is a problem in the windows installer package
    npm下载模块提速方法
    npm如何删除node_modules文件夹
  • 原文地址:https://www.cnblogs.com/SimonGao/p/5013698.html
Copyright © 2020-2023  润新知