ios UICollectionView flowLayout 没有正确包装单元格
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12927027/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView flowLayout not wrapping cells correctly
提问by lindon fox
I have a UICollectionView
with a FLowLayout. It will work as I expect most of the time, but every now and then one of the cells does not wrap properly. For example, the the cell that should be on in the first "column" of the third row if actually trailing in the second row and there is just an empty space where it should be (see diagram below). All you can see of this rouge cell is the left hand side (the rest is cut off) and the place it should be is empty.
我有一个UICollectionView
带有 FlowLayout 的。大多数情况下它会按我的预期工作,但时不时地其中一个单元格不能正确包裹。例如,应该在第三行的第一个“列”中的单元格,如果实际上在第二行后面,并且它应该在的地方只有一个空白区域(见下图)。你只能看到这个胭脂牢房的左侧(其余部分被切断),它应该在的地方是空的。
This does not happen consistently; it is not always the same row. Once it has happened, I can scroll up and then back and the cell will have fixed itself. Or, when I press the cell (which takes me to the next view via a push) and then pop back, I will see the cell in the incorrect position and then it will jump to the correct position.
这不会一直发生;它并不总是同一行。一旦发生,我可以向上滚动然后向后滚动,单元格将自行修复。或者,当我按下单元格(通过推动将我带到下一个视图)然后弹回时,我会看到单元格处于不正确的位置,然后它会跳到正确的位置。
The scroll speed seems to make it easier to reproduce the problem. When I scroll slowly, I can still see the cell in the wrong position every now and then, but then it will jump to the correct position straight away.
滚动速度似乎更容易重现问题。当我缓慢滚动时,我仍然可以时不时地看到错误位置的单元格,但它会立即跳到正确的位置。
The problem started when I added the sections insets. Previously, I had the cells almost flush against the collection bounds (little, or no insets) and I did not notice the problem. But this meant the right and left of the collection view was empty. Ie, could not scroll. Also, the scroll bar was not flush to the right.
当我添加部分插图时,问题就开始了。以前,我的单元格几乎与集合边界齐平(很少或没有插图),但我没有注意到这个问题。但这意味着集合视图的右侧和左侧是空的。即,无法滚动。此外,滚动条没有向右齐平。
I can make the problem happen on both Simulator and on an iPad 3.
我可以在模拟器和 iPad 3 上解决这个问题。
I guess the problem is happening because of the left and right section insets... But if the value is wrong, then I would expect the behavior to be consistent. I wonder if this might be a bug with Apple? Or perhaps this is due to a build up of the insets or something similar.
我想问题是由于左右部分插入而发生的......但是如果值错误,那么我希望行为是一致的。我想知道这是否可能是Apple的错误?或者这可能是由于插入物或类似物的堆积。
Follow up: I have been using this answerbelow by Nick for over 2 years now without a problem (in case people are wondering if there are any holes in that answer - I have not found any yet). Well done Nick.
跟进:我已经使用尼克在下面的这个答案超过 2 年没有问题(以防人们想知道该答案是否有任何漏洞 - 我还没有找到任何漏洞)。干得好尼克。
回答by Nick Snyder
There is a bug in UICollectionViewFlowLayout's implementation of layoutAttributesForElementsInRect that causes it to return TWO attribute objects for a single cell in certain cases involving section insets. One of the returned attribute objects is invalid (outside the bounds of the collection view) and the other is valid. Below is a subclass of UICollectionViewFlowLayout that fixes the problem by excluding cells outside of the collection view's bounds.
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
See this.
看到这个。
Other answers suggest returning YES from shouldInvalidateLayoutForBoundsChange, but this causes unnecessary recomputations and doesn't even completely solve the problem.
其他答案建议从 shouldInvalidateLayoutForBoundsChange 返回 YES,但这会导致不必要的重新计算,甚至不能完全解决问题。
My solution completely solves the bug and shouldn't cause any problems when Apple fixes the root cause.
我的解决方案完全解决了该错误,并且在 Apple 修复根本原因后不会造成任何问题。
回答by xxtesaxx
i discovered similar problems in my iPhone application. Searching the Apple dev forum brought me this suitable solution which worked in my case and will probably in your case too:
我在我的 iPhone 应用程序中发现了类似的问题。搜索 Apple 开发论坛为我带来了这个合适的解决方案,它适用于我的情况,也可能适用于您的情况:
Subclass UICollectionViewFlowLayout
and override shouldInvalidateLayoutForBoundsChange
to return YES
.
子类化UICollectionViewFlowLayout
并覆盖shouldInvalidateLayoutForBoundsChange
以返回YES
.
//.h
@interface MainLayout : UICollectionViewFlowLayout
@end
and
和
//.m
#import "MainLayout.h"
@implementation MainLayout
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
@end
回答by Peter Lapisu
Put this into the viewController that owns the collection view
将其放入拥有集合视图的 viewController
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.collectionView.collectionViewLayout invalidateLayout];
}
回答by Patrick Pijnappel
A Swift version of Nick Snyder's answer:
Nick Snyder 答案的 Swift 版本:
class NDCollectionViewFlowLayout : UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
let contentSize = collectionViewContentSize
return attributes?.filter { - (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;
}
.frame.maxX <= contentSize.width && - (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;
}
.frame.maxY < contentSize.height }
}
}
回答by monowerker
I've had this problem as well for a basic gridview layout with insets for margins. The limited debugging I've done for now is implementing - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
in my UICollectionViewFlowLayout subclass and by logging what the super class implementation returns, which clearly shows the problem.
对于带有边距插图的基本 gridview 布局,我也遇到过这个问题。我现在所做的有限调试是- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
在我的 UICollectionViewFlowLayout 子类中实现,并通过记录超类实现返回的内容,这清楚地显示了问题。
- (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;
}
By implementing - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
I can also see that it seems to return the wrong values for itemIndexPath.item == 30, which is factor 10 of my gridview's number of cells per line, not sure if that's relevant.
通过实施,- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
我还可以看到它似乎返回了 itemIndexPath.item == 30 的错误值,这是我的 gridview 每行单元格数的 10 倍,不确定这是否相关。
#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
With a lack of time for more debugging, the workaround I've done for now is reduced my collectionviews width with an amount equal to the left and right margin. I have a header that still needs the full width so I've set clipsToBounds = NO on my collectionview and then also removed the left and right insets on it, seems to work. For the header view to then stay in place you need to implement frame shifting and sizing in the layout methods that are tasked with returning layoutAttributes for the header view.
由于没有时间进行更多调试,我现在所做的解决方法是减少我的 collectionviews 宽度,其数量等于左右边距。我有一个仍然需要全宽的标题,所以我在我的 collectionview 上设置了 clipsToBounds = NO,然后还删除了它的左右插图,似乎有效。为了使标题视图保持原位,您需要在负责返回标题视图的 layoutAttributes 的布局方法中实现帧移动和调整大小。
回答by DrMickeyLauer
I have added a bug report to Apple. What works for me is to set bottom sectionInset to a value less than top inset.
我已向 Apple 添加了错误报告。对我有用的是将底部部分插入设置为小于顶部插入的值。
回答by Masa
I was experiencing the same cell-deplacing-problem on the iPhone using a UICollectionViewFlowLayout
and so I was glad finding your post. I know you are having the problem on an iPad, but I am posting this because I think it is a general issue with the UICollectionView
. So here is what I found out.
我在 iPhone 上使用 a 遇到了同样的单元格替换问题UICollectionViewFlowLayout
,所以我很高兴找到你的帖子。我知道您在 iPad 上遇到了问题,但我发布此信息是因为我认为这是UICollectionView
. 所以这就是我发现的。
I can confirm that the sectionInset
is relevant to that problem. Besides that the headerReferenceSize
also has influence whether a cell is deplaced or not. (This makes sense since it is needed for calcuating the origin.)
我可以确认这与sectionInset
该问题有关。除此之外,这headerReferenceSize
也影响细胞是否被置换。(这是有道理的,因为计算原点需要它。)
Unfortunately, even different screen sizes have to be taken into account. When playing around with the values for these two properties, I experienced that a certain configuration worked either on both (3.5" and 4"), on none, or on only one of the screen sizes. Usually none of them. (This also makes sense, since the bounds of the UICollectionView
changes, therefore I did not experience any disparity between retina and non-retina.)
不幸的是,即使是不同的屏幕尺寸也必须考虑在内。在处理这两个属性的值时,我体验到某个配置适用于两者(3.5" 和 4")、不适用或仅适用于其中一种屏幕尺寸。通常都没有。(这也是有道理的,因为UICollectionView
变化的界限,因此我没有体验到视网膜和非视网膜之间的任何差异。)
I ended up setting the sectionInset
and headerReferenceSize
depending on the screen size. I tried about 50 combinations until I found values under which the problem did not occure anymore and the layout was visually acceptable. It is very difficult to find values which work on both screen sizes.
我最终根据屏幕尺寸设置了sectionInset
和headerReferenceSize
。我尝试了大约 50 种组合,直到找到不再出现问题并且布局在视觉上可以接受的值。很难找到适用于两种屏幕尺寸的值。
So summarizing, I just can recommend you to play around with the values, check these on different screen sizes and hope that Apple will fix this issue.
总而言之,我只能建议您使用这些值,在不同的屏幕尺寸上检查这些值,并希望 Apple 能够解决这个问题。
回答by Andrey Seredkin
I've just encountered a similar issue with cells disappearing after UICollectionView scroll on iOS 10(got no problems on iOS 6-9).
我刚刚遇到了类似的问题,即在iOS 10上滚动 UICollectionView 后单元格消失(在 iOS 6-9 上没有问题)。
Subclassing of UICollectionViewFlowLayout and overriding method layoutAttributesForElementsInRect: doesn't work in my case.
UICollectionViewFlowLayout 的子类化和覆盖方法 layoutAttributesForElementsInRect: 在我的情况下不起作用。
The solution was simple enough. Currently I use an instance of UICollectionViewFlowLayout and set both itemSize and estimatedItemSize(I didn't use estimatedItemSize before) and set it to some non-zero size. Actual size is calculating in collectionView:layout:sizeForItemAtIndexPath: method.
解决方案很简单。目前我使用 UICollectionViewFlowLayout 的一个实例并设置itemSize 和estimatedItemSize(我之前没有使用过estimatedItemSize)并将其设置为一些非零大小。实际大小在 collectionView:layout:sizeForItemAtIndexPath: 方法中计算。
Also, I've removed a call of invalidateLayout method from layoutSubviews in order to avoid unnecessary reloads.
此外,我已经从 layoutSubviews 中删除了 invalidateLayout 方法的调用,以避免不必要的重新加载。
回答by Endama
I just experienced a similar issue but found a very different solution.
我刚刚遇到了类似的问题,但找到了一个非常不同的解决方案。
I am using a custom implementation of UICollectionViewFlowLayout with a horizontal scroll. I am also creating custom frame locations for each cell.
我正在使用带有水平滚动的 UICollectionViewFlowLayout 的自定义实现。我还为每个单元格创建自定义帧位置。
The problem that I was having was that [super layoutAttributesForElementsInRect:rect] wasn't actually returning all of the UICollectionViewLayoutAttributes that should be displayed on screen. On calls to [self.collectionView reloadData] some of the cells would suddenly be set to hidden.
我遇到的问题是 [super layoutAttributesForElementsInRect:rect] 实际上并没有返回所有应该显示在屏幕上的 UICollectionViewLayoutAttributes。在调用 [self.collectionView reloadData] 时,某些单元格会突然设置为隐藏。
What I ended up doing was to create a NSMutableDictionary that cached all of the UICollectionViewLayoutAttributes that I have seen so far and then include any items that I know should be displayed.
我最终做的是创建一个 NSMutableDictionary,它缓存了我目前看到的所有 UICollectionViewLayoutAttributes,然后包含我知道应该显示的任何项目。
[self.yourCollectionView reloadData]
Here is the category for NSMutableDictionary that the UICollectionViewLayoutAttributes are being saved correctly.
这是 NSMutableDictionary 的类别,UICollectionViewLayoutAttributes 被正确保存。
[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
回答by Yao Li
The above answers don't work for me, but after downloading the images, I replaced
以上答案对我不起作用,但下载图像后,我替换了
##代码##with
和
##代码##to refresh and it can show all cells correctly, you can try it.
刷新,它可以正确显示所有单元格,您可以尝试一下。