您的位置:首页 > 产品设计 > UI/UE

UICollectionView

2016-06-16 23:26 435 查看
UICollectionView

概念

UICollectionView继承自UIScrollView,支持水平和垂直滚动。UICollectionView和UITableView共享API设计,并在UITableView的基础上做了扩展。UICollectionView最强大、同时显著超出UITableView的特色就是其完全灵活的布局结构

UICollectionView也是由dataResource和delegate驱动的,为其显示的子视图集扮演容器,对它们的真实内容毫不知情

###UICollectionView的创建

代码创建collectionView

```objc

//  collectionView在初始化时,除了frame,还必须提供collectionView的布局参数

- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;

```

###UICollectionViewLayout类

创建collectionView时的必须对象参数,决定collectionView的布局方式。一般使用其子类UICollectionViewFlowLayout的对象作为参数,对collectionView的Cell进行流水布局

###使用UICollectionView的步骤

1. storyboard中拖拽UICollectionView控件到View中,或者代码创建UICollectionView控件,或者拖拽UICollectionViewController

2. 布局UICollectionViewCell

3. 设置UICollectionView的代理对象,实现dataSource数据源方法

4. 为UICollectionView注册Cell类以及重用ID。实现collectionView的数据源方法返回Cell时,根据重用ID去缓存池中找。如果没找到,则根据注册类创建一个

```objc

NSString *ID = @"cell";

[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:ID];

```

storyboard中设置collectionView的Cell的尺寸

![](Snip20160113_10.png)

##UICollectionViewFlowLayout布局类型类

collectionView的流水布局参数类。

**flowLayout**:通过一个接一个的放置Cell来建立布局,当需要时,插入横排或竖排的分栏符。通过自定义滚动方向、大小和Cell之间的距离,flow layout也可以在单行或单列中布局Cell

flowLayout的参数

![](Snip20160113_12.png)

###collectionViewCell的三种注册方式

####1  纯代码创建Cell

1. 创建继承自UICollectionViewCell的自定义类,重写父类的initWithFrame方法,创建所需的子控件并进行关联

2. 在viewDidLoad中,调用collectionView的registerClass方法,为自定义Cell类注册重用ID

![](Snip20160114_1.png)

3. 重写自定义Cell类数据模型属性的setter方法,为Cell子控件设置数据

4. 在Cell类的layoutSubviews方法中,设置子控件的frame

**注**:Cell的尺寸,是由collectionView来统一设置,或者通过collectionView的代理方法来确定的

####2   xib创建Cell

1. 创建继承自UICollectionViewCell的自定义类,同时生成xib文件

2. 在xib文件中,设置Cell的尺寸、子控件以及自动布局方式

3. 在viewDidLoad方法中,调用collectionView的registerNib方法注册Cell对应的xib文件以及重用ID。

**如果要在xib中设置Cell的重用ID,则必须与代码中注册的重用ID一致!**

![](Snip20160115_3.png)

4. 重写Cell类的数据模型属性的setter方法,为子控件设置数据

5. Cell的Size与xib中模板的大小无关,由collectionView的layout属性决定

6. 需要使用xib中Cell的尺寸作为实际尺寸时,可以用下面这个方法获取

![](Snip20160115_2.png)

####3   storyboard中使用collectionViewController自带Cell

1. 拖拽一个UICollectionViewController到storyboard面板中

2. 设置控制器的关联类为继承自UICollectionViewController的自定义类

3. 设置控制器自带Cell的模板子控件及其布局信息,注册Cell的重用ID

###设置collectionViewCell尺寸的方式

1. 通过设置collectionView的layout参数对象的itemSize属性来统一设置Cell的尺寸

2. 在storyboard中选中collectionView控件,属性中统一设置Cell的尺寸(storyboard中选中collectionView修改Cell的尺寸,实际修改的就是拖拽collectionView控件时自带flowLayout参数的Cell尺寸属性)

3. 通过实现collectionView的获取Cell尺寸的代理方法,来逐一设置每个Cell的尺寸

4. xib创建的Cell模板,不会有Cell的Size属性。如果没有其它方式设置Cell的Size,将采用默认Size

###collectionView的三个代理协议

* UICollectionViewDelegate

* UICollectionViewDataSource

* UICollectionViewDelegateFlowLayout

###collectionView展示lol英雄实现横屏后正常显示

```objc

//  屏幕将要旋转时,修改layout的itemSize属性,将collectionView重新布局Cell时,自动调整Cell的size

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{

    //  此时没有转换完毕,不能使用View的size,必须用方法自带的转换后的size参数

    self.flowLayout.itemSize = CGSizeMake(size.width - self.flowLayout.sectionInset.left - self.flowLayout.sectionInset.right, 60);

}

```

###疑问:collectionView实现lol英雄展示中,如何实现动态调整Cell的高度?

tableView中,可以根据设置Cell的contentView的底部与某一子控件的参数的约束,同时设置Cell预估行高与尺寸自动调整参数来实现Cell高度自动调整的要求。

而collectionView没有自动调整高度的参数,其flowLayout参数中也没有相关参数。怎么实现要求?

##collectionView图片无限滚动实现的思想

* collectionView只有3个Cell。每次滚动结束后,都让collectionView以非动画方式,滚回到第一个Cell(Cell从序数0开始)上,以保证每次拖拽滚动结束后,collectionView依然可以滚动

* 通过监听collectionView的滚动方向对应修改图片的全局索引值,每次collectionView自动滚回到第一个Cell,在collectionView创建Cell时,将对应的内容设置给collectionView的第一个Cell

* collectionView刷新Cell前关闭动画,刷新后重新开启动画

```objc

//  监听collectionView的滚动事件

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

    

    //  根据滚动结束后,collectionView的内容偏移值,计算出要显示的图片索引的变化

    CGFloat offset = self.collectionView.contentOffset.x;

    NSInteger offsetPage = (offset / self.view.frame.size.width) - 1;

    

    //  未翻页,什么都不做

    if (!offsetPage) {

        return;

    }

    

    //  +总页数对总页数取模,保证不越界

    self.imageIndex = (self.imageIndex + offsetPage + self.images.count) % self.images.count;

    

    //  每次滚动结束后,重新让collectionView滚动到中间Cell上

    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:1 inSection:0];

    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];

    

    //  必须要刷新Cell。

    //  每次新Cell出现时,都调用了代理返回Cell的方法,刷新了数据。为什么会出现Cell显示图片错误的问题?

    //  这个显示bug是由于collectionView多线程任务执行完成顺序先后造成的

    [UIView setAnimationsEnabled:NO];

    [self.collectionView reloadItemsAtIndexPaths:@[indexPath]];

    [UIView setAnimationsEnabled:YES];

}

```

###-viewDidAppear: 

程序启动viewDidLoad方法执行完时,collectionView的代理方法还没有调用。此时设置collectionView的Cell的相关属性和数据,是无意义的。必须要等到View被显示之后(所有的相关方法都调用执行完毕),才可以设置collectionView的Cell的相关参数和属性

---

##瀑布流布局实现思路及具体步骤

###思路

1. 定宽不定高:列宽相等,每一个item的高度由内容决定。

2. 每列下面一个Cell的Y轴起点,由上一个Cell的底部决定

3. collectionView的内容尺寸,由Cell最大的Y值决定

###实现步骤

1. 为col
bf4f
lectionView注册xib模板的Cell,设置Cell的子控件及约束

2. 实现collectionView的数据源协议方法,布局Cell

3. 自定义继承自UICollectionViewFlowLayout类的子类,重写父类的某些方法,手动计算每一个Cell对应的属性

4. 设置collectionView的flowLayout参数关联新建的自定义类

5. storyboard中给collectionView添加footerView,并关联自定义类,实现数据源方法中返回页脚View方法

6. 实现新增数据并刷新的方法,当FooterView 被创建后,自动执行并刷新数据

$$控制器类相关属性$$

```objc

@property (nonatomic, strong) NSMutableArray *goods;

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

@property (weak, nonatomic) IBOutlet ASWaterFlowLayout *flowLayout;

@property (nonatomic, weak) ASFooterView *footerView;

```

###自定义flowLayout类计算每个Cell的frame属性

$$属性$$

```objc

@property (nonatomic, assign) NSInteger columnCount;    //  排布列数

@property (nonatomic, weak) id<ASWaterFlowLayoutDelegate> delegate; // 获取Cell高度的代理对象

```

$$代理协议$$

```objc

@protocol ASWaterFlowLayoutDelegate <NSObject>

//  根据Cell序数及宽度,获取Cell高度

- (CGFloat)waterFlowLayoutGetCellHeight:(ASWaterFlowLayout *)waterFlowLayout

                withIndex:(NSInteger)index andCellWidth:(CGFloat)cellWidth;

@end

```

$$延展属性$$

```objc

@property (nonatomic, strong) NSMutableArray *arrayAttrs;    // 自定义保存每个Cell属性的数组

@property (nonatomic, strong) NSMutableDictionary *dictMaxY;  // 自定义保存每行当前Cell总高度的字典

@property (nonatomic, copy) NSString *keyMaxY;  // 字典中最大Y值对应的key

@property (nonatomic, copy) NSString *keyMinY;  // 字典中最小Y值对应的key

```

$$字典和数组的懒加载$$

```objc

- (NSMutableArray *)arrayAttrs{

    if (_arrayAttrs == nil)  _arrayAttrs = [NSMutableArray array];

    return _arrayAttrs;

}

- (NSMutableDictionary *)dictMaxY{

    if (!_dictMaxY) _dictMaxY = [NSMutableDictionary dictionary];

    return _dictMaxY;

}

```

$$重写prepareLayout方法$$

```objc

- (void)prepareLayout{

    [super prepareLayout];

    

    //  初始化字典,让字典拥有三组键值

    for (int i=0; i<self.columnCount; i++) {

        NSString *key = [NSString stringWithFormat:@"%d", i];

        self.dictMaxY[key] = @"0";

    }

    

    //  每次刷新数据准备布局collectionView时,清除所有Cell的layout属性

    [self.arrayAttrs removeAllObjects];

    

    //  计算所有Cell的属性

    //  获取collectionView第0组的总Cell数

    NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];

    for (int i=0; i<cellCount; i++) {

        

        //  获取当前Cell所在的坐标

        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];

        //  获取对应Cell的属性数据

        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath];

        

        //  将当前Cell的属性信息添加到Cell属性数组中

        [self.arrayAttrs addObject:attr];

    }

    

    //  计算footerView的属性

    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];

    UICollectionViewLayoutAttributes *attrFooter = [UICollectionViewLayoutAttributes

            layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:indexPath];

    

    //  根据内容最大Y值,设置footerView的属性

    NSString *maxHeight = self.keyMaxY;

    attrFooter.frame = CGRectMake(0, [self.dictMaxY[maxHeight] floatValue],

            self.collectionView.frame.size.width, self.footerReferenceSize.height);

    

    [self.arrayAttrs addObject:attrFooter];

}

```

**注:**属性数组中,可以重复为指定indexPath的Cell添加多个不同的属性实例,最后布局时,以最后一个被添加进数组的属性实例为准。但是,不可以添加多于1个footerView的属性实例!新添加进的footerView的属性实例不会在布局时替换旧值,而是直接冲突报错!

![](Snip20160117_2.png)

$$计算每个item的frame并返回$$

```objc

//  计算Cell的属性。控制器每布局一个Cell,都会来调用一次这个方法

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{

    

    //  计算每一个Cell的frame

    CGFloat contentW = self.collectionView.frame.size.width - self.sectionInset.left -

            self.sectionInset.right - self.minimumInteritemSpacing * (self.columnCount - 1);

    //  计算每个Cell的宽,代理获取对应的高

    CGFloat cellW = contentW / self.columnCount;

    CGFloat cellH = [self.delegate waterFlowLayoutGetCellHeight:self withIndex:indexPath.item andCellWidth:cellW];

    

   //  将Cell布局在Y值最小的列

    NSInteger col = [self.keyMinY integerValue];

    CGFloat cellX = self.sectionInset.left + col * (cellW + self.minimumInteritemSpacing);

    

    NSString *keyCol = [NSString stringWithFormat:@"%ld", col];

    CGFloat cellY = [self.dictMaxY[keyCol] floatValue];

    

    //  更新字典中布局Cell所在列的最大高度

    CGFloat maxY = cellY + cellH + self.minimumInteritemSpacing;

    self.dictMaxY[keyCol] = [NSString stringWithFormat:@"%f", maxY];

    

    //  修改对应Cell在数组中保存的属性值

    UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    attr.frame = CGRectMake(cellX, cellY, cellW, cellH);

    

    //  返回Cell的属性值

    return attr;

}

```

$$返回可视范围内所有Cell的布局属性泛型数组$$

```objc

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{

    return self.arrayAttrs;

}

```

$$返回collectionView的contentSize$$

```objc

- (CGSize)collectionViewContentSize{

    return CGSizeMake(0, [self.dictMaxY[self.keyMaxY] floatValue] + self.footerReferenceSize.height);

}

```

$$获取字典中最大/小Y值对应的key$$

```objc

- (NSString *)keyMaxY{

    //  假定最大的key为第0组键值的key

    __block NSString *keyMaxY = @"0";

    //  block方式遍历字典,比较Y值,获取最大的key

    [self.dictMaxY enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull value, BOOL * _Nonnull stop) {

        if ([self.dictMaxY[keyMaxY] floatValue] < [value floatValue]) {

            keyMaxY = key;

        }

    }];

    return keyMaxY;

}

```

###刷新数据功能模块

$$更新数据数组$$

类似于懒加载,根据新数据plist文件,构建模型数组,数组中的模型元素添加到原数据数组中

$$滚出FooterView之后的刷新数据事件$$

```objc

//  监听collectionView的滚动位置。当footerView被显示出来后,执行这个方法中的内容

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    //  FooterView没有出现或者指示器还在旋转时,返回

    if (!self.footerView || [self.footerView.activityIndicator isAnimating])   return;

    

    [self.footerView.activityIndicator startAnimating];    //  指示器开始动画

    

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        [self reloadData];  //  更新数据数组

        [self.collectionView reloadData];   // collectionView刷新数据

        [self.footerView.activityIndicator stopAnimating]; // 指示器停止动画

        self.footerView = nil; // 释放FooterView

    });

}

```
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: