UICollectionView flowLayout没有正确包装单元格

时间:2012-10-17 04:18:07

标签: ios ios6 uicollectionview uicollectionviewcell

我有一个带有FLowLayout的UICollectionView。它可以像我期望的那样工作,但是偶尔会有一个单元格无法正确包装。例如,如果实际上在第二行中尾随,则应该在第三行的第一个“列”中打开的单元格,并且它应该是一个空的空间(参见下图)。所有你能看到的这个胭脂细胞是左手边(其余的被切掉),它应该是空的。

这不会持续发生;它并不总是同一行。一旦它发生了,我可以向上滚动然后再回来,单元格将自行修复。或者,当我按下单元格(通过推动将我带到下一个视图)然后弹回时,我将看到单元格位于错误的位置,然后它将跳转到正确的位置。

滚动速度似乎可以更容易地重现问题。当我慢慢滚动时,我仍然可以偶尔看到单元格处于错误的位置,但是它会立即跳到正确的位置。

当我添加了insets部分时,问题就出现了。以前,我的细胞几乎与集合边界齐平(很少或没有插入),我没有注意到这个问题。但这意味着集合视图的右侧和左侧是空的。即,无法滚动。此外,滚动条没有向右冲洗。

我可以在模拟器和iPad 3上发生问题。

我猜问题正在发生,因为左右部分插入...但如果值是错误的,那么我希望行为是一致的。我想知道这可能是Apple的错误吗?或者这可能是由于插入物的堆积或类似的东西。

Illustration of problem and settings


跟进:我一直在使用Nick下面的this answer超过2年没有问题(如果有人想知道答案中是否有漏洞 - 我有还没找到)。做得好尼克。

11 个答案:

答案 0 :(得分:92)

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

请参阅this

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

我的解决方案完全解决了这个错误,并且在Apple修复根本原因时不应该导致任何问题。

答案 1 :(得分:7)

我在iPhone应用程序中发现了类似的问题。搜索Apple开发论坛给我带来了这个合适的解决方案,这个解决方案适用于我的情况,也可能适合你的情况:

子类UICollectionViewFlowLayout并覆盖shouldInvalidateLayoutForBoundsChange以返回YES

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

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

答案 2 :(得分:7)

将此信息放入拥有集合视图

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

答案 3 :(得分:7)

斯威夫特的尼克斯奈德的回答:

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 }
    }
}

答案 4 :(得分:5)

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

- (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倍,不确定它是否相关。 / p>

- (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;
}

由于没有时间进行更多调试,我现在所做的解决方法是减少我的collectionviews宽度,其数量等于左右边距。我有一个仍然需要全宽度的标题,所以我在我的集​​合视图上设置了clipsToBounds = NO,然后还删除了左边和右边的插图,似乎工作。为了使标题视图保持原位,您需要在布局方法中实现帧移位和大小调整,这些布局方法的任务是返回标题视图的layoutAttributes。

答案 5 :(得分:4)

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

答案 6 :(得分:3)

我使用UICollectionViewFlowLayout在iPhone上遇到了相同的细胞替换问题,所以我很高兴找到你的帖子。我知道你在iPad上遇到了这个问题,但我发帖是因为我认为这是UICollectionView的一般问题。所以这就是我发现的。

我可以确认sectionInset与该问题相关。除此之外,headerReferenceSize还影响细胞是否被移除。 (这是有道理的,因为需要它来计算原点。)

不幸的是,必须考虑不同的屏幕尺寸。在玩这两个属性的值时,我发现某个配置在(3.5“和4”),无,或仅在一个屏幕大小上都有效。通常都不是。 (这也是有道理的,因为UICollectionView的界限发生了变化,因此我在视网膜和非视网膜之间没有任何差异。)

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

总结一下,我可以建议你玩这些值,在不同的屏幕尺寸上检查这些值,并希望Apple能解决这个问题。

答案 7 :(得分:3)

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

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

解决方案很简单。目前我使用UICollectionViewFlowLayout的实例并将设置为itemSize和estimatedItemSize (之前我没有使用estimatedItemSize)并将其设置为某个非零大小。 实际大小是在collectionView:layout:sizeForItemAtIndexPath:method。

中计算的

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

答案 8 :(得分: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;
}

以下是正确保存UICollectionViewLayoutAttributes的NSMutableDictionary的类别。

#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

答案 9 :(得分:2)

以上答案对我不起作用,但在下载图像后,我更换了

QObject

[self.yourCollectionView reloadData]

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

答案 10 :(得分:0)

这可能有点晚,但如果可能,请确保在prepare()设置属性。

我的问题是细胞正在铺设,然后在layoutAttributesForElements进行更新。当新细胞进入视野时,这会导致闪烁效应。

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