UICollectionView的 bug


在开发中我们难免会用到UICollectionView,一般常规用法是没有任何问题的,但是,比如在用UICollectionView实现瀑布流效果时,自定义每个cell的frame属性的时候就会出现在滑动过程中有些cell一会显示一会消失的奇葩问题(特别是cell较多的时候,总会滑动到某个地方的时候出现cell突然消失的效果)。更奇葩的是,有的情况是在6s上显示正常,在5s上会出现一会消失一会显示。

比如在我的demo中是这样子的:

一会显示一会消失的效果图.gif

什么Bug?

在网上搜索关键字cell

disappearing in UICollection view或UICollectionView some cell not

appear或UICollectionView滚动的时候cell消失,你会发现网上有很多人遇到过这种问题,下面附上各大论坛上的图和链接:

苹果官方开发者论坛的Problem of cell disappearing in UICollection view in ios 10 only

苹果论坛.png

来自stackoverflow的UICollectionView's cell disappearing

stackoverflow.png

来自segmentfault的UICollectionView滚动的时候会出现cell消失的情况

segmentfault.png

有人说通过将UICollectionView的bounces属性设置为NO,有人说这是UICollectionView的bug(提到苹果官方论坛也没人回复),有人推荐使用PSTCollectionView这个轮子(用UIScrollView的子类实现类似UICollectionView的效果)。

下面先来看看造成cell一会显示一会消失的效果的主要代码:

- (void)prepareLayout {

[superprepareLayout];

}

#pragma mark - CollectionView的滚动范围

- (CGSize)collectionViewContentSize

{

CGFloat width = self.collectionView.frame.size.width;

CGFloat maxY = [self maxOrignYInSection:_framesArray.count -1];

returnCGSizeMake(width, maxY + _rowHeight + self.sectionInset.bottom);

}

#pragma mark - 所有cell和view的布局属性

//sectionheader sectionfooter decorationview collectionviewcell的属性都会走这个方法

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

{

NSArray *tmpArray = [superlayoutAttributesForElementsInRect:rect];

NSMutableArray *array = [NSMutableArray arrayWithCapacity:tmpArray.count];

for(NSInteger i =0; i < tmpArray.count; i++){

UICollectionViewLayoutAttributes *attrs = [tmpArray objectAtIndex:i];

UICollectionElementCategory category = attrs.representedElementCategory;

if(category == UICollectionElementCategoryCell){

[array addObject:[self layoutAttributesForItemAtIndexPath:attrs.indexPath]];

}elseif(category == UICollectionElementCategorySupplementaryView){

UICollectionViewLayoutAttributes *theAttrs = [self layoutAttributesForSupplementaryViewOfKind:attrs.representedElementKind

atIndexPath:attrs.indexPath];

[array addObject:theAttrs];

}

}

returnarray;

}

详细复现代码在ReappearBugCode

分析代码,寻找Bug

首先,我们这里是用UICollectionView实现一个高度固定,宽度不固定的瀑布流效果,每个cell的宽度根据文字内容计算的,每一行显示不全的时候自动换行,在cell展示的时候通过获取cell对应的布局属性来把这个cell展示在指定的位置上。

其次,在cell全部显示的情况下观察,cell的frame全部是正确的,这就说明我们代码计算每一个cell的布局属性是没有问题的。并且UICollectionView的可滑动范围contentSize的计算也是没有问题的。

最后,这些一会显示一会消失的cell是在UICollectionView滑动到某个区域时出现的,这就说明在这个区域内的cell布局获取的有问题(计算没问题)。

我们知道自定义的UICollectionViewLayout时必须实现并且会按顺序执行的方法如下:

 - (void)prepareLayout;//step 1

- (CGSize)collectionViewContentSize;//step 2

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;//step 3

由上面的分析可见,问题应该出在layoutAttributesForElementsInRect:方法中,我们在快要滑动到出现异常的区域时在这个方法处加个断点。当滑动到出现异常的区域时,看到tmpArray为空了,说明问题确实出在了这里。

cell显示不正常的区域.png

因为我们已经对每个Cell都自定义了布局,调用[super

layoutAttributesForElementsInRect:rect]返回的布局属性的集合并不是我们想要的。所以在这里,我们需要在这里获取UICollectionView当前可见的返回,然后自己返回当前处在该区域内的cell的布局属性集合。

修改代码,解决Bug

解决思路和步骤:

在prepareLayout方法中计算所有cell的frame并缓存起来,可提高UICollectionView滑动的流畅性

在collectionViewContentSize方法中根据上面计算出来的frame返回UICollectionView可滑动的范围

在layoutAttributesForElementsInRect:方法中先拿到UICollectionView当前可见范围,然后遍历上面计算的frame,判断哪些cell或header应该展示在该区域内,把这些cell和header的布局属性放到一个数组中返回。

修改后的主要代码:(以下为一组代码)

#pragma mark - 重写父类的方法,实现瀑布流布局

//step1

- (void)prepareLayout {

[superprepareLayout];

[self calculateFrames];

}

#pragma mark - CollectionView的滚动范围

//step2

- (CGSize)collectionViewContentSize

{

CGFloat width = self.collectionView.frame.size.width;

returnCGSizeMake(width, _contentHeight);

}

#pragma mark - 所有cell和view的布局属性

//sectionheader sectionfooter decorationview collectionviewcell的属性都会走这个方法

//step3

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

{

NSMutableArray *attributesArray = [NSMutableArray array];

CGPoint offset = self.collectionView.contentOffset;

CGRect visibleRect = CGRectMake(0, offset.y, CGRectGetWidth(self.collectionView.frame), CGRectGetHeight(self.collectionView.frame));

for(NSInteger section =0; section < _framesArray.count; section++){

NSArray *currentSectionFrames = _framesArray[section];

for(NSInteger row =0; row < currentSectionFrames.count; row++){

CGRect currentFrame = [currentSectionFrames[row] CGRectValue];

NSIndexPath *currentIndexPath = [NSIndexPath indexPathForRow:row inSection:section];

if(currentFrame.origin.y + currentFrame.size.height >= visibleRect.origin.y &&

currentFrame.origin.y <= visibleRect.origin.y + visibleRect.size.height){

//first section header should show

if(row ==0&& section ==0){

UICollectionViewLayoutAttributes *headerAttr = [[self layoutAttributesForSupplementaryViewOfKind:@"UICollectionElementKindSectionHeader"

atIndexPath:currentIndexPath] copy];

CGRect frame = headerAttr.frame;

frame.origin.y =0;

headerAttr.frame = frame;

[attributesArray addObject:headerAttr];

}

//cell should show

UICollectionViewLayoutAttributes *cellAttrs = [[self layoutAttributesForItemAtIndexPath:currentIndexPath] copy];

cellAttrs.frame = currentFrame;

[attributesArray addObject:cellAttrs];

//next section header should show

if(row == currentSectionFrames.count -1&& section +1< _framesArray.count &&

currentFrame.origin.y + currentFrame.size.height + self.sectionInset.bottom < visibleRect.origin.y + visibleRect.size.height){

UICollectionViewLayoutAttributes *headerAttr = [[self layoutAttributesForSupplementaryViewOfKind:@"UICollectionElementKindSectionHeader"

atIndexPath:[NSIndexPath indexPathForRow:0inSection:section +1]] copy];

CGFloat y = [self contentHeightInSection:section];

CGRect frame = headerAttr.frame;

frame.origin.y = y;

headerAttr.frame = frame;

[attributesArray addObject:headerAttr];

}

}

}

}

returnattributesArray;

}

修改后的效果:

修改后的效果图.gif

详细代码见:YLTagsChooser 如果大家有更好的解决办法,欢迎反馈。

http://www.cocoachina.com/ios/20170918/20516.html

http://www.cocoachina.com/ios/20170918/20516.html

你可能感兴趣的:(UICollectionView的 bug)