Большой UICollectionViewCell перестает отображаться при прокрутке
То же поведение UICollectionView
, как описано здесь, было вызвано этим вопросом. Несмотря на то, что я решил опубликовать свой собственный, потому что я провел дальнейшие расследования, я не хотел публиковать в комментарии или редактировать вопрос, упомянутый выше.
Что происходит?:
Когда большие ячейки отображаются в UICollectionView
с UICollectionViewFlowLayout
, после прокрутки представления коллекции до определенного смещения ячейки исчезнут.
При прокрутке дальше, пока другая камера не попадет в видимую область, исчезающая/скрытая ячейка снова станет видимой.
Я тестировал с вертикальным списком коллекции прокрутки и полноразмерными ячейками, но я уверен, что это также произойдет с аналогичными настройками для горизонтальной прокрутки.
Что такое большие ячейки?:
Описанное поведение происходит с ячейками, превышающими вдвое больше высоты дисплея (960.f + 1.f
на 3,5-дюймовых дисплеях, 1136.f + 1.f
на 4 дюйма).
Что именно происходит?:
Когда смещение прокрутки в представлении коллекции превышает cell.frame.origin.y + displayHeightOfHardware
, свойство скрытых ячеек установлено на YES
и вызывает вызов -collectionView:didEndDisplayingCell:forItemAtIndexPath:
(например, первая ячейка изменяется на скрытую, когда scrollingOffset.y
достигает 481.f
на 3, 5-дюймовый iPhone).
Как описано выше, при прокрутке до появления следующей ячейки скрытая ячейка снова отображается (т.е. скрытое свойство изменяется на NO
) и, кроме того, при прокрутке достаточно далеко ячейка никогда не исчезнет снова, t, независимо от того, где вы прокручиваете.
Это изменяется при работе с ячейками, большими, чем triple-display-height (1441.f/1705.f
). Они показывают одно и то же поведение, но оно остается неизменным, независимо от того, насколько далеко они прокручиваются вверх и вниз.
Что еще?:
Ситуация не может быть исправлена путем переопределения -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
для возврата YES
.
Ячейки не могут быть принудительно отображены с установкой скрытого свойства на NO
программно после их скрытия (например, в didEndDisplayingCell
)
Итак, в чем вопрос?:
Я уверен, что это ошибка в UICollectionView/Controller/Cell/Layout
, и я отправлю TSI в Apple. Но тем временем: Есть ли у кого-нибудь идеи для быстрого решения hack?
Ответы
Ответ 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
Ответ 2
У меня есть обходное решение, которое, похоже, работает для меня и не должно запускать правила Apple для приложений iOS.
Ключ является наблюдением, что проблема связана с большими ячейками. Я работал над этим, гарантируя, что один край ячейки находится в пределах видимой области области прокручиваемого содержимого. Очевидно, что вам нужно подклассифицировать класс UICollectionViewFlowLayout или UICollectionViewLayout в зависимости от ваших потребностей и использовать значение contentOffset для отслеживания, где вы находитесь в UIScrollView.
Я также должен был обеспечить:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
возвращает YES или сталкивается с исключением среды выполнения, указывающим, что макет был недействителен. Я держу край большей ячейки привязанным к левому краю в моем случае. Таким образом, вы можете избежать обнаружения ошибочных границ пересечения для этих более крупных ячеек.
Это создает больше работы в зависимости от того, как вы хотите, чтобы содержимое ячейки отображалось, когда ширина/высота ячейки обновляется по мере прокрутки. В моем случае, subviews внутри ячейки относительно просты и не требуют много возиться с.
В соответствии с просьбой приведен пример моего 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
, чтобы определить, пересекается ли ячейка. Затем цикл продолжается, пока пересечение не сработает. И если хотя бы одна горизонтальная ячейка найдена, петля сломается. Надеюсь, что это поможет.
Ответ 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 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
Ответ 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.