UICollectionView的重大bug?自定义UICollectionView样式的你必须得知道这个

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

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

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

什么Bug?

在网上搜索关键字cell disappearing in UICollection viewUICollectionView some cell not appearUICollectionView滚动的时候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 {
    [super prepareLayout];
}

#pragma mark - CollectionView的滚动范围
- (CGSize)collectionViewContentSize
{
    CGFloat width = self.collectionView.frame.size.width;
    CGFloat maxY = [self maxOrignYInSection:_framesArray.count - 1];
    return CGSizeMake(width, maxY + _rowHeight + self.sectionInset.bottom);
}

#pragma mark - 所有cell和view的布局属性
//sectionheader sectionfooter decorationview collectionviewcell的属性都会走这个方法
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *tmpArray = [super layoutAttributesForElementsInRect: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]];
        }else if (category == UICollectionElementCategorySupplementaryView){
            UICollectionViewLayoutAttributes *theAttrs = [self layoutAttributesForSupplementaryViewOfKind:attrs.representedElementKind
                                                                                              atIndexPath:attrs.indexPath];
            [array addObject:theAttrs];
        }
    }
    return array;
}

详细复现代码在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<UICollectionViewLayoutAttributes *> *)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 {
    [super prepareLayout];
    [self calculateFrames];
}

#pragma mark - CollectionView的滚动范围
//step2
- (CGSize)collectionViewContentSize
{
    CGFloat width = self.collectionView.frame.size.width;
    return CGSizeMake(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:0 inSection:section + 1]] copy];
                    CGFloat y = [self contentHeightInSection:section];
                    CGRect frame = headerAttr.frame;
                    frame.origin.y = y;
                    headerAttr.frame = frame;
                    [attributesArray addObject:headerAttr];
                }
            }
        }
    }
    return attributesArray;
}

修改后的效果:

修改后的效果图.gif

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349