UICollectionView flowLayout无法正确包装单元格


79

我有UICollectionView一个FLowLayout。大部分时间都可以正常工作,但是有时其中一个单元无法正确包装。例如,如果单元格实际上在第二行中尾随,则应该在第三行的第一个“列”中打开的单元格,并且应该有一个空白(请参见下图)。您可以看到的该胭脂细胞只有左侧(其余部分被切除),应该放置的地方是空的。

这并非始终如一;它并不总是同一行。一旦发生,我可以向上滚动然后向后滚动,单元格将自动修复。或者,当我按下该单元格(通过按一下将其带到下一个视图)然后弹出时,我会看到该单元格处于错误的位置,然后它将跳至正确的位置。

滚动速度似乎可以更轻松地重现该问题。缓慢滚动时,仍然可以时不时地看到该单元格处于错误的位置,但是随后它将立即跳到正确的位置。

当我添加节插图时,问题开始了。以前,我的单元格几乎与收集边界齐平(很少或没有插图),而且我没有注意到问题。但这意味着集合视图的左右为空。即,无法滚动。另外,滚动条也不在右边。

我可以使问题同时在Simulator和iPad 3上发生。

我猜是由于左侧和右侧部分的插入而发生了问题。但是如果值错误,那么我希望行为是一致的。我想知道这是否可能是Apple的错误?也许这是由于插图或类似内容的积累。

问题和设置说明


跟进:我使用Nick的答案已经有两年多了,一直没有问题(如果人们想知道答案是否有漏洞,我还没有发现)。尼克,做得好。

Answers:


94

UICollectionViewFlowLayout的layoutAttributesForElementsInRect的实现中存在一个错误,该错误导致它在某些涉及节插入的情况下,为单个单元格返回两个属性对象。返回的属性对象之一无效(超出集合视图的范围),而另一个有效。下面是UICollectionViewFlowLayout的子类,它通过排除集合视图范围之外的单元格来解决此问题。

// NDCollectionViewFlowLayout.h
@interface NDCollectionViewFlowLayout : UICollectionViewFlowLayout
@end

// NDCollectionViewFlowLayout.m
#import "NDCollectionViewFlowLayout.h"
@implementation NDCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
  NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
  for (UICollectionViewLayoutAttributes *attribute in attributes) {
    if ((attribute.frame.origin.x + attribute.frame.size.width <= self.collectionViewContentSize.width) &&
        (attribute.frame.origin.y + attribute.frame.size.height <= self.collectionViewContentSize.height)) {
      [newAttributes addObject:attribute];
    }
  }
  return newAttributes;
}
@end

看到这个

其他答案建议从shouldInvalidateLayoutForBoundsChange返回YES,但这会导致不必要的重新计算,甚至不能完全解决问题。

我的解决方案完全解决了该错误,并且在Apple解决根本原因后不会造成任何问题。


5
仅供参考,此错误还会随着水平滚动而出现。用y替换x并用高度替换宽度使此补丁起作用。
Patrick Tescher

谢谢!我刚开始使用的CollectionView玩(如果你想垃圾邮件苹果这一点,这里的rdar参考openradar.appspot.com/12433891
Vinzzz

1
@richarddas不,您不想检查rect是否相交。实际上,所有单元格(有效或无效)都将与集合视图的边界rect相交。您想检查rect的任何部分是否超出范围,这就是我的代码所做的。
Nick Snyder

2
@Rpranata自iOS 7.1起,此错误尚未修复。叹。
消极的

2
也许我使用的是这种错误,但是在Swift的iOS 8.3上,这导致右边的子视图曾经被切掉而根本不显示。还有谁?
sudo 2015年

8

将其放入拥有集合视图的viewController中

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    [self.collectionView.collectionViewLayout invalidateLayout];
}

你把它放在哪里?
fatuhoku

进入拥有集合视图的
viewController

3
我的问题是细胞完全消失了。该解决方案有所帮助-但这会导致不必要的重新加载。仍然可以正常工作.. thx!
pawi 2014年

1
如果从viewController为我调用,则会导致无限循环
Hofi

正如DHennessy13指出的那样,此当前解决方案很好,但可能并不完善,因为当旋转屏幕时,它将使Layout无效(在大多数情况下不应这样做)。一种改进可能是将标志设置为invalidateLayout仅一次。
心教堂

7

我在我的iPhone应用程序中发现了类似的问题。在Apple开发人员论坛上搜索后,我找到了这个合适的解决方案,该解决方案对我而言适用,并且可能对您而言也适用:

子类UICollectionViewFlowLayout并重写shouldInvalidateLayoutForBoundsChange以返回YES

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

//.m
#import "MainLayout.h"
@implementation MainLayout
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}
@end

这只能部分解决我遇到的这个问题。当该行出现时,单元格确实的正确位置。但是,在它出现之前,我仍然有一个单元格出现在侧面。
Daniel Wood

小心-这样做会导致每次滚动都运行布局。这会严重影响性能。
fatuhoku

7

尼克·斯奈德(Nick Snyder)的答案的快速版本:

class NDCollectionViewFlowLayout : UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)
        let contentSize = collectionViewContentSize
        return attributes?.filter { $0.frame.maxX <= contentSize.width && $0.frame.maxY < contentSize.height }
    }
}

1
这完全使CollectionViewCell消失了……还有其他解决方案吗?
Giggs

5

对于带有边距插图的基本gridview布局,我也遇到了这个问题。我现在所做的有限调试是- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect在UICollectionViewFlowLayout子类中实现,并记录超类实现返回的结果,这清楚地表明了问题。

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attrsList = [super layoutAttributesForElementsInRect:rect];

    for (UICollectionViewLayoutAttributes *attrs in attrsList) {
        NSLog(@"%f %f", attrs.frame.origin.x, attrs.frame.origin.y);
    }

    return attrsList;
}

通过实现,- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath我还可以看到似乎为itemIndexPath.item == 30返回了错误的值,这是我的gridview每行单元格数量的10倍,不确定是否相关。

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    UICollectionViewLayoutAttributes *attrs = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    NSLog(@"initialAttrs: %f %f atIndexPath: %d", attrs.frame.origin.x, attrs.frame.origin.y, itemIndexPath.item);

    return attrs;
}

由于没有足够的时间进行更多调试,因此我现在要做的解决方法是减小collectionview的宽度,使其宽度等于左右边距。我的标头仍然需要完整宽度,因此我在collectionview上设置了clipsToBounds = NO,然后还删除了它的左右插图,这似乎可行。为了使标题视图保持在原位,您需要在布局方法中实现移帧和调整大小,这些方法负责返回标题视图的layoutAttributes。


感谢您提供@monowerker的额外信息。我认为我的问题是在添加插图时开始的(我已将此添加到问题中)。我将尝试您的调试方法,看看是否能告诉我任何信息。我也可以尝试您的工作。
林顿·福克斯

这很可能是UICFL / UICL中的一个错误,我将在有空的时候尝试获取雷达,这是一些您可以参考的rdar编号的讨论。twitter.com/steipete/status/258323913279410177
monowerker,2012年

4

我已经向Apple添加了一个错误报告。对我有用的是将bottom sectionInset设置为小于top inset的值。


3

我在使用a的iPhone上遇到了相同的单元更换问题UICollectionViewFlowLayout,因此很高兴找到您的帖子。我知道您在iPad上遇到了问题,但我发布了此信息,因为我认为这是笔记本电脑的普遍问题UICollectionView。所以这就是我发现的。

我可以确认sectionInset与该问题有关。除此之外headerReferenceSize细胞是否移位也有影响。(这很有意义,因为它是计算原点所必需的。)

不幸的是,甚至必须考虑不同的屏幕尺寸。在处理这两个属性的值时,我发现某种配置既可以在(3.5“和4”)上工作,也可以在一个屏幕尺寸上都不工作,或者仅在其中一个屏幕尺寸上工作。通常都没有。(这也是有道理的,由于UICollectionView变化的范围,因此我在视网膜和非视网膜之间没有任何差异。)

我最终根据屏幕尺寸设置sectionInsetheaderReferenceSize。我尝试了约50种组合,直到找到不再出现问题的值,并且布局在视觉上可以接受。很难找到适用于两种屏幕尺寸的值。

综上所述,我只是建议您使用这些值,并在不同的屏幕尺寸上检查这些值,并希望Apple可以解决此问题。


3

我刚刚遇到类似的问题,在iOS 10上UICollectionView滚动后,单元格消失了(在iOS 6-9上没有问题)。

UICollectionViewFlowLayout的子类化和覆盖方法layoutAttributesForElementsInRect:在我的情况下不起作用。

解决方案非常简单。目前,我使用UICollectionViewFlowLayout的实例并设置itemSize和EstimatedItemSize(我之前没有使用)并将其设置为一些非零大小。实际大小是在collectionView:layout:sizeForItemAtIndexPath:方法中计算的。

另外,为了避免不必要的重载,我从layoutSubviews中删除了对invalidateLayout方法的调用。


uicollectionviewflowlayout中的itemsize和estimateItemsize设置在哪里?
加勒特·考克斯

UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout分配]初始化]; [flowLayout setItemSize:CGSizeMake(200,200)]; [flowLayout setEstimatedItemSize:CGSizeMake(200,200)]; self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
Andrey Seredkin '16

设置
EstimatedItemSize

2

我刚刚遇到了类似的问题,但是找到了一个完全不同的解决方案。

我正在使用UICollectionViewFlowLayout的自定义实现和水平滚动。我还在为每个单元格创建自定义框架位置。

我遇到的问题是,[super layoutAttributesForElementsInRect:rect]并未真正返回应该在屏幕上显示的所有UICollectionViewLayoutAttributes。在调用[self.collectionView reloadData]时,某些单元格会突然设置为隐藏。

我最终要做的是创建一个NSMutableDictionary,它缓存了到目前为止我已经看到的所有UICollectionViewLayoutAttributes,然后包括了我应该显示的所有项目。

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray * attrs = [NSMutableArray array];
    CGSize calculatedSize = [self calculatedItemSize];

    [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
        NSIndexPath * idxPath = attr.indexPath;
        CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
        if (CGRectIntersectsRect(itemFrame, rect))
        {
            attr = [self layoutAttributesForItemAtIndexPath:idxPath];
            [self.savedAttributesDict addAttribute:attr];
        }
    }];

    // We have to do this because there is a bug in the collection view where it won't correctly return all of the on screen cells.
    [self.savedAttributesDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray * cachedAttributes, BOOL *stop) {

        CGFloat columnX = [key floatValue];
        CGFloat leftExtreme = columnX; // This is the left edge of the element (I'm using horizontal scrolling)
        CGFloat rightExtreme = columnX + calculatedSize.width; // This is the right edge of the element (I'm using horizontal scrolling)

        if (leftExtreme <= (rect.origin.x + rect.size.width) || rightExtreme >= rect.origin.x) {
            for (UICollectionViewLayoutAttributes * attr in cachedAttributes) {
                [attrs addObject:attr];
            }
        }
    }];

    return attrs;
}

这是NSMutableDictionary的类别,UICollectionViewLayoutAttributes已正确保存。

#import "NSMutableDictionary+CDBCollectionViewAttributesCache.h"

@implementation NSMutableDictionary (CDBCollectionViewAttributesCache)

- (void)addAttribute:(UICollectionViewLayoutAttributes*)attribute {

    NSString *key = [self keyForAttribute:attribute];

    if (key) {

        if (![self objectForKey:key]) {
            NSMutableArray *array = [NSMutableArray new];
            [array addObject:attribute];
            [self setObject:array forKey:key];
        } else {
            __block BOOL alreadyExists = NO;
            NSMutableArray *array = [self objectForKey:key];

            [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *existingAttr, NSUInteger idx, BOOL *stop) {
                if ([existingAttr.indexPath compare:attribute.indexPath] == NSOrderedSame) {
                    alreadyExists = YES;
                    *stop = YES;
                }
            }];

            if (!alreadyExists) {
                [array addObject:attribute];
            }
        }
    } else {
        DDLogError(@"%@", [CDKError errorWithMessage:[NSString stringWithFormat:@"Invalid UICollectionVeiwLayoutAttributes passed to category extension"] code:CDKErrorInvalidParams]);
    }
}

- (NSArray*)attributesForColumn:(NSUInteger)column {
    return [self objectForKey:[NSString stringWithFormat:@"%ld", column]];
}

- (void)removeAttributesForColumn:(NSUInteger)column {
    [self removeObjectForKey:[NSString stringWithFormat:@"%ld", column]];
}

- (NSString*)keyForAttribute:(UICollectionViewLayoutAttributes*)attribute {
    if (attribute) {
        NSInteger column = (NSInteger)attribute.frame.origin.x;
        return [NSString stringWithFormat:@"%ld", column];
    }

    return nil;
}

@end

我也使用了水平滚动,我设法使用您的解决方案解决了该问题,但是在对其他视图执行搜索并返回后,当有多余的项目没有按均等划分时,内容大小似乎是错误的。
morph85 '16

我找到了解决此问题的解决方案,该问题是在执行Segue后返回到集合视图后,单元会被隐藏。尽量不要在collectionViewFlowLayout中设置estimatedItemSize;直接设置itemSize。
morph85 '16

2

上面的答案对我不起作用,但是下载图像后,我替换了

[self.yourCollectionView reloadData]

[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

刷新,它可以正确显示所有单元格,您可以尝试一下。


0

这可能会有点晚,但是请确保在prepare()可能的情况下设置属性。

我的问题是这些单元正在布置,然后在更新layoutAttributesForElements。当新细胞出现时,这会导致闪烁效果。

通过将所有属性逻辑移至prepare,然后在UICollectionViewCell.apply()其中设置它们,消除了闪烁,并创建了黄油平滑单元格显示displaying

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.