滚动时大型 UICollectionViewCell 停止显示
Posted
技术标签:
【中文标题】滚动时大型 UICollectionViewCell 停止显示【英文标题】:Large UICollectionViewCell stopped being displayed when scrolling 【发布时间】:2013-01-10 09:10:06 【问题描述】:此处描述的UICollectionView
的相同行为已导致this question。即使我决定发布自己的帖子,因为我做了进一步的调查,我不想在评论或编辑上述问题时发表。
会发生什么?
当大单元格显示在UICollectionView
和UICollectionViewFlowLayout
中时,将集合视图滚动到某个偏移量后,单元格将消失。
当进一步滚动直到另一个单元格进入可见区域时,消失/隐藏的单元格再次变得可见。
我使用垂直滚动集合视图和全角单元格进行了测试,但我很确定,类似的水平滚动设置也会发生这种情况。
什么是大细胞?:
所描述的行为发生在高于两倍显示高度的单元格(960.f + 1.f
在 3.5 英寸显示器上,1136.f + 1.f
在 4 英寸显示器上)。
究竟发生了什么?:
当集合视图的滚动偏移量超过cell.frame.origin.y + displayHeightOfHardware
时,单元格隐藏属性设置为YES
并调用-collectionView:didEndDisplayingCell:forItemAtIndexPath:
(例如,当scrollingOffset.y
在3 上到达481.f
时,第一个单元格变为隐藏, 5 英寸 iPhone)。
如上所述,当滚动到下一个单元格出现时,隐藏单元格会再次显示(即隐藏属性更改为NO
),此外,当滚动足够远时,单元格将永远不会再次消失t,无论您滚动到哪里。
当使用大于三倍显示高度 (1441.f/1705.f
) 的单元格时,这种情况会发生变化。这些显示相同的行为,但无论向上和向下滚动多远,它都保持不变。
还有什么?
这种情况无法通过覆盖-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
以返回YES
来解决。
在单元格被隐藏后,不能通过将 hidden 属性设置为 NO
来强制显示单元格(例如在 didEndDisplayingCell
中)
那么,问题是什么?:
我很确定,这是UICollectionView/Controller/Cell/Layout
中的一个错误,我将在 Apple 提交一份 TSI。但与此同时:有人对快速破解解决方案有任何想法吗?
【问题讨论】:
您是否从 Apple 那里得到了关于此问题的良好回复? 【参考方案1】:我有一个非常肮脏的内部解决方案来解决这个问题:
@interface UICollectionView ()
- (CGRect)_visibleBounds;
@end
@interface MyCollectionView : UICollectionView
@end
@implementation MyCollectionView
- (CGRect)_visibleBounds
CGRect rect = [super _visibleBounds];
rect.size.height = [self heightOfLargestVisibleCell];
return rect;
- (float)heightOfLargestVisibleCell
// do your calculations for current max cellHeight and return it
return 1234;
@end
【讨论】:
当您说“内部解决方案”时,您是指无法通过应用商店流程的解决方案吗? @JackCox_visibleBounds
是privat-API,所以可能不是。大多数情况下,苹果不会检测到此类黑客攻击,但无法保证。
谢谢。我就是这么想的。
有效!因为 - 正如你所说 - 这可能会在 Apple 的批准过程中被拒绝,我会等待他们(TSI 已经提交)听他们说什么。
骗人张贴雷达的链接,这样我就可以复制它?我希望看到这个问题得到解决。【参考方案2】:
我有一个似乎对我有用的解决方法,不应该违反 Apple 的 ios 应用程序规则。
关键是观察到大细胞边界是问题所在。我通过确保单元格的一个边缘位于可滚动内容区域的可视区域内来解决这个问题。您显然需要根据需要对 UICollectionViewFlowLayout 类或 UICollectionViewLayout 进行子类化,并使用 contentOffset 值来跟踪您在 UIScrollView 中的位置。
我还必须确保:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
返回 YES 或面临运行时异常,指示布局无效。在我的例子中,我保持较大单元格的边缘绑定到左边缘。这样,您可以避免对这些较大的单元格进行错误的边界交叉检测。
这确实会创建更多工作,具体取决于您希望如何呈现单元格的内容,因为单元格的宽度/高度会在您滚动时更新。就我而言,单元格内的子视图相对简单,不需要太多的摆弄。
这里的要求是我的layoutAttributesInRect
的一个例子
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
NSMutableArray* attributes = [NSMutableArray array];
NSArray *vertical = myVerticalCellsStore.cells;
NSInteger startRow = floor(rect.origin.y * (vertical.count)/ (vertical.count * verticalViewHeight + verticalViewSpacing * 2));
startRow = (startRow < 0) ? 0 : startRow;
for (NSInteger i = startRow; i < vertical.count && (rect.origin.y + rect.size.height >= i * verticalViewHeight); i++)
NSArray *horizontals = myHorizontalStore.horizontalCells;
UICollectionViewLayoutAttributes *verticalAttr = [self layoutAttributesForSupplementaryViewOfKind:@"vertical" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
if (CGRectIntersectsRect(verticalAttr.frame, rect))
[attributes addObject:verticalAttr];
BOOL foundAnElement = NO;
for (NSInteger j = 0 ; j < horizontals.count; j++)
MYViewLayoutAttributes *attr = (MyViewLayoutAttributes *)[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
if (CGRectIntersectsRect(rect, attr.frame))
[attributes addObject: attr];
foundAnElement = YES;
else if (foundAnElement)
break;
return attributes;
这是我的净化代码。基本上我计算的是第一个单元格应该基于单元格高度。在我的情况下,这是固定的,所以计算很容易。但是我的水平元素有不同的宽度。所以内部循环实际上是要找出正确数量的水平单元格以包含在属性数组中。在那里我使用CGRectIntersectsRect
来确定单元格是否相交。然后循环继续进行,直到交叉点失败。如果至少找到一个水平单元格,则循环将中断。希望对您有所帮助。
【讨论】:
您对 layoutAttributesForElementsInRect 中的布局属性或单元格框架进行此框架调整?如果在什么时候在单元格上? 我在 layoutAttributesForElementsInRect 实现中做。 +1 用于标识将单元格的边缘保持在集合视图的边界内可以防止这种情况发生。我正在努力让我的单元格布局它们的子视图以弥补这一点,但这似乎对性能产生了负面影响。无赖。 你能提供一个layoutAttributesForElementsInRect
实现的例子吗?
@Yaman 我已经更新了答案以包含我必须创建的layoutAttributesForElementsInRect
实现的示例。希望对您有所帮助。【参考方案3】:
我的解决方案与乔纳森的解决方案基本相同,但属于一个类别,因此您不必使用自己的子类。
@implementation UICollectionView (MTDFixDisappearingCellBug)
+ (void)load
NSError *error = nil;
NSString *visibleBoundsSelector = [NSString stringWithFormat:@"%@isib%@unds", @"_v",@"leBo"];
if (![[self class] swizzleMethod:NSSelectorFromString(visibleBoundsSelector) withMethod:@selector(mtd_visibleBounds) error:&error])
FKLogErrorVariables(error);
- (CGRect)mtd_visibleBounds
CGRect bounds = [self mtd_visibleBounds]; // swizzled, no infinite loop
MTDDiscussCollectionViewLayout *layout = [MTDDiscussCollectionViewLayout castedObjectOrNil:self.collectionViewLayout];
// Don`t ask me why, but there's a visual glitch when the collection view is scrolled to the top and the max height is too big,
// this fixes it
if (bounds.origin.y <= 0.f)
return bounds;
bounds.size.height = MAX(bounds.size.height, layout.maxColumnHeight);
return bounds;
@end
【讨论】:
但请注意,这可能会导致性能问题,因为可见边界可能会变得非常大,并且 UICollectionView 需要计算可见边界中每个单元格的框架。 我会添加一个条件来仅在 iOS6 上调整它。对于每个想知道的人......“visibleBounds”被认为是私有 API,如果你不混淆选择器,就会被拒绝。【参考方案4】:我发现只有在使用子类 UICollectionViewLayoutAttributes 并且该属性类没有正确的 isEqual: 方法时才会出现此问题。
例如:
@implementation COGridCollectionViewLayoutAttributes
- (id)copyWithZone:(NSZone *)zone
COGridCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
attributes.isInEditMode = _isInEditMode;
return attributes;
- (BOOL)isEqual:(id)other
if (other == self)
return YES;
if (!other || ![[other class] isEqual:[self class]])
return NO;
if ([((COGridCollectionViewLayoutAttributes *) other) isInEditMode] != [self isInEditMode])
return NO;
return [super isEqual:other];
@end
工作,但最初我有:
return YES;
这是在 iOS 7 上。
【讨论】:
你能否补充更多细节,问题与 isEqual 有什么关系?以上是关于滚动时大型 UICollectionViewCell 停止显示的主要内容,如果未能解决你的问题,请参考以下文章
滚动时大型 UICollectionViewCell 停止显示
如何将显示的 sql 数据加载到 wpf 可滚动列表视图中?大型集合(20k+)而不会遇到内存问题?