UICollectionView flowLayout没有正确包装单元格(iOS)
我有一个UICollectionView
的UICollectionView。 它会像大多数时间一样工作,但是现在,其中一个单元格不能正确包装。 例如,第三行第一个“列”中应该打开的单元格,如果实际上在第二行后面,并且只有一个空白空间(参见下图),那么该单元格应该位于第三行的第一个“列”中。 所有你能看到的这个胭脂细胞都是左边的(其余的被切除),它应该是空的。
这并不一致, 它并不总是相同的行。 一旦发生,我可以向上滚动,然后返回,单元格将自己修复。 或者,当我按下单元格(通过按压将我带到下一个视图)然后popup时,我会看到单元格位置不正确,然后跳转到正确的位置。
滚动速度似乎更容易重现问题。 当我慢慢地滚动时,我仍然可以不时地看到单元错误的位置,然后马上跳到正确的位置。
当我添加节插入时,问题就开始了。 以前,我的单元格几乎与收集边界齐平(很less或没有插入),我没有注意到这个问题。 但是这意味着收集视图的右侧和左侧是空的。 即,不能滚动。 另外,滚动条并不是在右边。
我可以在模拟器和iPad 3上发生问题。
我猜这个问题正在发生,因为左侧和右侧部分插入…但如果价值是错误的,那么我会期望行为是一致的。 我不知道这可能是苹果的错误吗? 或者,这可能是由于内嵌物或类似物的堆积造成的。
任何解决scheme/解决方法,赞赏。
跟进 :我已经在下面用尼克的这个答案超过6个月, 12个月,现在没有问题(如果人们想知道这个答案是否有漏洞 – 我还没有find任何问题)。 做得好尼克。
在UICollectionViewFlowLayout的layoutAttributesForElementsInRect的实现中存在一个错误,这个错误导致它在涉及部分插入的某些情况下返回单个单元的TWO属性对象。 其中一个返回的属性对象是无效的(在集合视图的范围之外),另一个是有效的。 下面是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
看到这个
其他答案build议从shouldInvalidateLayoutForBoundsChange返回YES,但这会导致不必要的重新计算,甚至不能完全解决问题。
我的解决scheme彻底解决了这个错误,当Apple修复根本原因时,不应该引起任何问题。
我在iPhone应用程序中发现了类似的问题。 search苹果开发论坛给我带来了这个合适的解决scheme,在我的情况下工作,也可能在你的情况:
子类UICollectionViewFlowLayout
并重写shouldInvalidateLayoutForBoundsChange
以返回YES
。
//.h @interface MainLayout : UICollectionViewFlowLayout @end
和
//.m #import "MainLayout.h" @implementation MainLayout -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ return YES; } @end
把它放到拥有集合视图的viewController中
- (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self.collectionView.collectionViewLayout invalidateLayout]; }
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 } } }
我也有这个问题,以及为边缘的insets基本gridview布局。 我现在所做的有限的debugging是在我的UICollectionViewFlowLayout子类中实现- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
,并通过logging超类的实现返回什么,这清楚地显示了问题。
- (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; }
由于缺乏更多debugging时间,我现在所做的解决方法是减less我的collections浏览宽度,数量等于左右页边距。 我有一个标题,仍然需要全宽,所以我已经设置clipsToBounds = NO在我的collectionview,然后也删除左侧和右侧的插图,似乎工作。 为了保持标题视图,您需要在布局方法中实现框架移动和尺寸调整,这些方法的任务是为标题视图返回layoutAttributes。
我已经向苹果添加了一个错误报告。 什么对我来说是设置底部sectionInset小于顶部插入的值。
我正在使用UICollectionViewFlowLayout
在iPhone上遇到相同的单元格replace问题,所以很高兴find您的post。 我知道你在iPad上遇到了这个问题,但是我发布了这个,因为我认为这是UICollectionView
一个普遍问题。 所以这是我发现的。
我可以确认sectionInset
与这个问题有关。 除此之外, headerReferenceSize
也影响一个单元格是否被取代。 (这是有道理的,因为它需要用来计算原点。)
不幸的是,即使不同的屏幕尺寸也必须考虑在内。 当玩弄这两个属性的值时,我经历了一个特定的configuration在(3.5“和4”)上都没有,或者只在一个屏幕大小上工作。 通常没有一个。 (这也是有道理的,因为UICollectionView
的边界改变了,所以我没有经历视网膜和非视网膜之间的任何差异。)
我结束了根据屏幕大小设置sectionInset
和headerReferenceSize
。 我尝试了大约50种组合,直到我发现问题不再出现,并且布局在视觉上是可以接受的。 要在两种屏幕尺寸上find适用的值是非常困难的。
所以总结一下,我只是推荐你玩一下这些值,在不同的屏幕尺寸上查看,希望苹果能够解决这个问题。
我刚刚遇到类似的问题,但发现一个非常不同的解决scheme。
我正在使用水平滚动自定义UICollectionViewFlowLayout的实现。 我也为每个单元格创build自定义框架位置。
我遇到的问题是[super layoutAttributesForElementsInRect:rect]实际上并没有返回应该在屏幕上显示的所有UICollectionViewLayoutAttributes。 在对[self.collectionView reloadData]的调用中,一些单元格会突然被设置为隐藏。
我最终做的是创build一个NSMutableDictionarycaching到目前为止我看到的所有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
上面的答案不适用于我,但下载图像后,我取而代之
[self.yourCollectionView reloadData]
同
[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
刷新,它可以正确显示所有细胞,你可以尝试。
我刚刚遇到类似的问题,在iOS 10上滚动UICollectionView后,单元格消失(iOS 6-9没有问题)。
UICollectionViewFlowLayout的子类化和重写方法layoutAttributesForElementsInRect:在我的情况下不起作用。
解决scheme很简单。 目前我使用UICollectionViewFlowLayout的一个实例,并设置itemSize和estimatedItemSize (我之前没有使用estimatedItemSize),并将其设置为一些非零大小。 实际大小在collectionView:layout:sizeForItemAtIndexPath:方法中进行计算。
另外,我已经从layoutSubviews中删除了invalidateLayout方法的调用,以避免不必要的重新加载。