IOS —— CollectionView 所遇到的坑
哈喽,还是俺,记录一下今天复习的成果(collectionView还要学习,这老脸丢不起)
重点是日历方法以及collectionView的一些细节补充!
那么闲话少说上代码上分析
1. xib创建日历
我们知道创建collectionView时,必须要用到initWithFrame: collectionFlowLayout 方法并且还要创建一个collectionFlowLayout对象,设置属性云云
代码上是这样创建的,但xib呢? 在开发中有些时候页面控制没太多约束时,尝试xib开发也不是一种坏事。so?go
既然要使用collectionView创建日历,那么准备需要什么?
1.cell的h.m.xib 文件 ,并在文件里创建大小与cell相同的label
2.headerView xib 文件 ,并在文件里创建7个均等分割屏幕大小的label,修改text为星期日、星期一等 (为什么是星期日先后面会提及)
对应视图控制器与控件之间的关联,以及代理对象的设置。 此处容易疏漏导致报错
准备完毕后开始激活CollectionView了!
[_collectView registerNib:[UINib nibWithNibName:@"DateCell" bundle:nil] forCellWithReuseIdentifier:@"DateCell"];
collectionView和tableView不一样,他的属性大多数需要注册才可使用。这里为注册DateCell
[_collectView registerNib:[UINib nibWithNibName:@"DateHeadView" bundle:nil] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"DateHeadView"];
这里为注册headerView,同样的注册collectionView的footerView一样是在这里,只需要将supplementaryViewofKind的值的末尾改成Footer即可。
collectionView 俩把火枪 cell、以及flowLayout,缺一不可。那么接下来就是flowLayout
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; //cell 大小 flowLayout.itemSize = CGSizeMake(ScreenWidth / 7, ScreenWidth / 7); //cell 行距 flowLayout.minimumLineSpacing = 0; //cell 距离 flowLayout.minimumInteritemSpacing = 0; //headerView Size flowLayout.headerReferenceSize = CGSizeMake(ScreenWidth, 50); [_collectView setCollectionViewLayout:flowLayout];
然后的就是代理对象的实现
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"DateHeadView" forIndexPath:indexPath]; return headerView; } return nil; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { DateCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"DateCell" forIndexPath:indexPath]; cell.textLabel.text = [_dateArr[indexPath.row] description]; return cell; }
由于是在…没什么好讲的,就仅仅是保证注册对象命名一致,重用、数据源绑定即可
那么会有人问了,日历部分呢?嗯
这里要引出一个 NSCalendar的类。以前制作日历总是非常复杂,获取时间方式也是。需要琐碎代码控制
往后苹果就统一出了这个类方便后来的人自定义日历
先贴代码
+ (NSInteger)totalDaysInMonthFromDate:(NSDate *)date { NSRange dayRange = [[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date]; return dayRange.length; } //@"2016-09-29" YYYY-MM-dd /* 字符串格式、转时间 时间差 */ + (NSInteger)totalDaysInMonthFromDateStr:(NSString *)dateStr { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; //调整时区 [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [formatter setDateFormat:@"YYYY-MM-dd"]; NSDate *date = [formatter dateFromString:dateStr]; return [self totalDaysInMonthFromDate:date]; } + (NSInteger)weekDayMonthOfFirstDayFromDate:(NSDate *)date { //美国时间是将星期天视作一周的第一天。 NSInteger firstDayOfMonthInt = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:date]; return firstDayOfMonthInt ; } + (NSInteger)weekDayMonthOfFirstDayFromDateStr:(NSString *)dateStr { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [formatter setDateFormat:@"YYYY-MM-dd"]; NSDate *date = [formatter dateFromString:dateStr]; return [self weekDayMonthOfFirstDayFromDate:date]; }
第一个方法里就用到了
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date]
有些人就会问了,这个是啥啊?还能是啥日历方法呗
在日历类里,从输入的数据源的月份中查询有当月有多少天。
输入当前的12月,那么range的范围长度就是31。这就能直接返回给collectionView的numberOfSection方法,得知cell得创建几个
Unit属性NSCalendarUnit里有还有许多枚举,这里就不一一列举。有需要点进去根据需求输入就好
接下来的问题是,如何确保每个月的第一天添加到适合的位置? 总不能每个月的第一天都是星期天吧。
那么这里再用了一个方法,查找到当月第一天距离星期天有几天
+ (NSInteger)weekDayMonthOfFirstDayFromDate:(NSDate *)date { //美国时间是将星期天视作一周的第一天。 NSInteger firstDayOfMonthInt = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:date]; return firstDayOfMonthInt ; }
我知道有些人会问,为什么要距离星期天几天云云
注释里也有写到,因为美国时间是将星期日视为一周的第一天,所以我们通过方法获取当前月份的第一周中第一天距离星期天有几天。将获得的数据与当前月份的数据一起添加到一个NSArray中。便完成了日历的数据源了!
- (void)dataHandle:(NSString *)datestrr { NSInteger firstWeekDay = [DateModel weekDayMonthOfFirstDayFromDate:[NSDate date]]; NSInteger dayCount = [DateModel totalDaysInMonthFromDate:[NSDate date]]; //补前面空白 for (int i = 0; i< firstWeekDay; i++) { [_dateArr addObject:@""]; } //数据源为0-30 所以需要做+1操作 for (int i =0; i< dayCount; i++) { [_dateArr addObject:@(i+1)]; } //前边补了空白末尾也不能漏呀 int leftDay = 0; if (!(_dateArr.count%7) ){ leftDay = 7 - _dateArr.count%7; } for (int i = 0; i < leftDay; i++) { [_dateArr addObject:@""]; } [_collectView reloadData]; }
剩下的,就是collectionView的一些小操作了。就不一一提及
2.瀑布流
说起瀑布流,那一定是开发们绕不开的坑。学都学过。
瀑布流需要通过设置布局属性来实现,说道布局属性……中美合拍,文体俩开花?
不不不 说的是 UICollectionFlowLayout。我们只要重写一下布局属性就可以简单的实现了
但是这里还是会遇到一些坑。
collectionView Xib的设置这里就略过,和上文同样的设置方式。
Layout的源码先pro出来
- (void)prepareLayout { //准备数据 对每一个cell布局进行初始化 [layoutAry removeAllObjects]; [_originYArr removeAllObjects]; // row for (int i = 0; i<_collectViewRowCount; i++) { [_originYArr addObject:@(0)]; } NSInteger cellCount = [self.collectionView numberOfItemsInSection:0]; for (int i = 0; i< cellCount; i++) { // 处理每一个cell的布局属性 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath]; [layoutAry addObject:attribute]; } } //初始化布局方法 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; float cellSizeWidth = [UIScreen mainScreen].bounds.size.width/ _collectViewRowCount; //如果这个cell是一个图片,那么就返回图片高度 float cellSizeHeight = 50 + arc4random_uniform(100); //通过indexPath 算出相应高度 float cellX = cellSizeWidth * (indexPath.row %3); //第一个cell的Y //动态计算布局 float cellY = [_originYArr[indexPath.row%3] floatValue]; _originYArr[indexPath.row %3] = @(cellY +cellSizeHeight); attributes.frame = CGRectMake(cellX, cellY, cellSizeWidth, cellSizeHeight); return attributes; } //tableView 所有高度计算完,算出contentSize //滑动不了的原因 - (CGSize)collectionViewContentSize { float maxHeight =[_originYArr[0] floatValue]; for (int i = 1; i<_collectViewRowCount; i++) { if (maxHeight < [_originYArr[i] floatValue]) { maxHeight = [_originYArr[i] floatValue]; } } CGSize size = CGSizeMake([UIScreen mainScreen].bounds.size.width, maxHeight); return size; }
我们假定瀑布流为3列
首先我们需要用到这个方法,字面意思 准备布局 相当于View will appear 。在准备布局的代码里准备数据。
- (void)prepareLayout
如注释提及,首先得清空数据。并且因为瀑布流中每一列的初始y值都为0,后续的才参差不齐。
那么我们需要准备一个存储高度的数组,专门用于计算高度。接下来便是遍历当前collectionView numberOfSection中cell的count数。
然后分别进行设置布局。
当设置布局时,就用到接下来的代理方法。
这里的float设置,字面上说的不如自己画个图,画个图就懂了…实在不懂利用代码打个断点了解一下
大致说说就是计算出当前Cell的x、y、width,然后通过不断往初始高度height中添加同纵列的新cell的height。
然后设置attributes.frame值。返回即可。
这时候我们运行代码会发现,跑是跑的通,但是就是不能滑动。
因为collectionView与tableView特性类似。我们单独设置了每个cell的大小。所以此时系统并没有帮忙获取当前collectionView的contentSIze。主要内容的尺寸
这里我们通过方法,判断布局Array数组中数值最大的值,获取并且赋值给contentSize即可
结语:虽然与tableView类似,但是很多情况下collectionView的坑比TableView还多。满头疼的还是。
这里就暂时这么多,因为比较简单就不多次阐述。
在下文笔不太好,大多写给自己看留个底供自己查阅(自黑一下),水平欠佳也请看官们见谅
再接再厉吧!