UICollectionView flowLayout이 셀을 올바르게 래핑하지 않음


79

나는이 UICollectionViewFlowLayout가와. 대부분의 경우 예상대로 작동하지만 때때로 세포 중 하나가 제대로 포장되지 않습니다. 예를 들어, 실제로 두 번째 행에서 후행하고 있어야하는 곳에 빈 공간이있는 경우 세 번째 행의 첫 번째 "열"에 있어야하는 셀입니다 (아래 다이어그램 참조). 이 루즈 셀에서 볼 수있는 것은 왼쪽 (나머지는 잘림)이고 그것이 있어야 할 곳은 비어 있습니다.

이것은 일관되게 발생하지 않습니다. 항상 같은 행이 아닙니다. 그런 다음 위로 스크롤 한 다음 뒤로 스크롤하면 셀이 고정됩니다. 또는 셀을 눌렀다가 (푸시를 통해 다음보기로 이동 함) 뒤로 튀어 나오면 셀이 잘못된 위치에있는 것을보고 올바른 위치로 점프합니다.

스크롤 속도가 문제를 재현하기 쉽게 만드는 것 같습니다. 천천히 스크롤하면 가끔 잘못된 위치에있는 셀을 볼 수 있지만 곧바로 올바른 위치로 점프합니다.

섹션 삽입을 추가했을 때 문제가 시작되었습니다. 이전에는 셀이 컬렉션 경계 (삽입이 거의 또는 전혀 없음)에 대해 거의 플러시되었고 문제를 알아 차리지 못했습니다. 그러나 이것은 컬렉션 뷰의 오른쪽과 왼쪽이 비어 있음을 의미합니다. 즉, 스크롤 할 수 없습니다. 또한 스크롤 막대가 오른쪽으로 플러시되지 않았습니다.

Simulator와 iPad 3 모두에서 문제가 발생하도록 할 수 있습니다.

왼쪽과 오른쪽 섹션 삽입으로 인해 문제가 발생하는 것 같습니다 ...하지만 값이 잘못되면 동작이 일관 될 것으로 기대합니다. 이것이 Apple의 버그인지 궁금합니다. 또는 아마도 이것은 삽입물이나 이와 유사한 것의 축적 때문일 것입니다.

문제 및 설정 그림


후속 조치 : 나는 2 년 넘게 Nick의 아래 대답을 문제없이 사용하고 있습니다 (사람들이 그 대답에 구멍이 있는지 궁금해하는 경우-아직 찾지 못했습니다). 잘 했어 Nick.

답변:


94

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

참조 .

다른 답변은 shouldInvalidateLayoutForBoundsChange에서 YES를 반환하는 것을 제안하지만 이로 인해 불필요한 재 계산이 발생하고 문제가 완전히 해결되지도 않습니다.

내 솔루션은 버그를 완전히 해결하며 Apple이 근본 원인을 해결할 때 문제를 일으키지 않아야합니다.


5
참고로이 버그는 가로 스크롤로도 잘립니다. x를 y로, 너비를 높이로 바꾸면이 패치가 작동합니다.
Patrick Tescher 2012

감사! 방금 collectionView로 플레이하기 시작했습니다 (Apple에 스팸을 보내려면 여기에 rdar 참조 openradar.appspot.com/12433891 )
Vinzzz

1
@richarddas 아니요, rects가 교차하는지 확인하고 싶지 않습니다. 실제로 모든 셀 (유효 또는 유효하지 않음)은 컬렉션 뷰의 경계 사각형과 교차합니다. rect의 일부가 경계를 벗어나는지 확인하고 싶습니다. 이것이 내 코드가하는 일입니다.
Nick Snyder 2014

2
@Rpranata iOS 7.1부터이 버그는 수정되지 않았습니다. 한숨.
nonamelive

2
아마도 내가 이것을 잘못 사용하고 있지만 Swift의 iOS 8.3에서는 잘 렸던 오른쪽의 하위보기가 전혀 표시되지 않습니다. 다른 사람?
sudo

8

컬렉션 뷰를 소유하는 viewController에 이것을 넣으십시오.

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

그걸 어디에 두세요?
fatuhoku

콜렉션 뷰를 소유하는 viewController로
Peter Lapisu

3
내 문제는 세포가 완전히 사라 졌다는 것입니다. 이 솔루션이 도움이되었지만 이로 인해 불필요한 재로드가 발생합니다. 아직도 작동하고 있습니다 .. thx!
파위

1
나를 위해 viewController에서 호출하면 무한 루프가 발생합니다
Hofi

DHennessy13 에서 언급 했듯이이 현재 솔루션은 좋지만 화면을 회전 할 때 invalidateLayout이 발생하므로 불완전 할 수 있습니다 (대부분의 경우 안 됨). 한 invalidateLayout번만 플래그를 설정하는 것이 개선 될 수 있습니다 .
Cœur

7

내 iPhone 응용 프로그램에서 비슷한 문제를 발견했습니다. Apple 개발자 포럼을 검색하면 제 경우에 효과가 있었고 아마도 귀하의 경우에도 마찬가지 일 것입니다.

아강 UICollectionViewFlowLayout 및 재정의 shouldInvalidateLayoutForBoundsChange를 반환 YES합니다.

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

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

이것은 내가이 문제로 겪고있는 문제를 부분적으로 만 해결합니다. 행이 나타날 때 셀은 실제로 올바른 위치로 이동합니다. 그러나 그것이 나타나기 직전에 나는 여전히 측면에서 나타나는 세포를 얻습니다.
Daniel Wood

주의 — 이렇게하면 스크롤 할 때마다 레이아웃이 실행됩니다. 이는 성능에 심각한 영향을 줄 수 있습니다.
fatuhoku

7

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 { $0.frame.maxX <= contentSize.width && $0.frame.maxY < contentSize.height }
    }
}

1
이로 인해 CollectionViewCell이 완전히 사라졌습니다. 다른 가능한 해결책이 있습니까?
Giggs

5

여백이 삽입 된 기본 gridview 레이아웃에서도이 문제가 발생했습니다. 지금까지 수행 한 제한된 디버깅은 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rectUICollectionViewFlowLayout 하위 클래스에서 구현하고 문제를 명확하게 보여주는 수퍼 클래스 구현이 반환하는 내용을 로깅하는 것입니다.

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

더 많은 디버깅을위한 시간이 부족하여 지금까지 수행 한 해결 방법은 왼쪽 및 오른쪽 여백과 동일한 양으로 collectionviews 너비를 줄이는 것입니다. 여전히 전체 너비가 필요한 헤더가 있으므로 collectionview에서 clipsToBounds = NO를 설정 한 다음 왼쪽 및 오른쪽 삽입도 제거하고 작동하는 것 같습니다. 헤더 뷰가 제자리에 유지 되려면 헤더 뷰에 대한 layoutAttributes를 반환하는 작업을 수행하는 레이아웃 메서드에서 프레임 이동 및 크기 조정을 구현해야합니다.


추가 정보 @monowerker에 감사드립니다. 나는 삽입물을 추가했을 때 내 문제가 시작되었다고 생각합니다 (나는 이것을 질문에 추가했습니다). 나는 당신의 디버깅 방법을 시도하고 그것이 나에게 아무것도 말하는지 볼 것입니다. 나도 당신의 작업을 시도 할 수 있습니다.
lindon fox

이것은 UICFL / UICL의 버그 일 가능성이 큽니다. 시간이있을 때 레이더를 제출할 것입니다. 여기에 참조 할 수있는 몇 가지 rdar 번호에 대한 토론이 있습니다. twitter.com/steipete/status/258323913279410177
monowerker

4

Apple에 버그 보고서를 추가했습니다. 나를 위해 작동하는 것은 bottom sectionInset을 top inset보다 작은 값으로 설정하는 것입니다.


3

나는 a를 사용하여 iPhone에서 동일한 세포 교체 문제를 경험하고 UICollectionViewFlowLayout있었기 때문에 귀하의 게시물을 찾아서 기뻤습니다. 나는 당신이 iPad에서 문제가 있다는 것을 알고 있지만, 나는 그것이 일반적인 문제라고 생각하기 때문에 이것을 게시하고 있습니다.UICollectionView . 그래서 여기에 제가 알아 낸 것이 있습니다.

sectionInset해당 문제와 관련이 있음을 확인할 수 있습니다 . 그 외에도headerReferenceSize 세포의 이동 여부에 영향을 미칩니다. (원점을 계산하는 데 필요하므로 의미가 있습니다.)

불행히도 다른 화면 크기도 고려해야합니다. 이 두 속성의 값을 가지고 놀 때 특정 구성이 (3.5 "및 4") 둘 다에서 작동하거나 없음 또는 화면 크기 중 하나에서만 작동하는 것을 경험했습니다. 보통 그들 중 누구도. (이것은 또한 의미가 있습니다.UICollectionView 변화 있으므로 망막과 비 망막 사이의 차이를 경험하지 않았습니다.)

화면 크기에 따라 sectionInset및 설정을 끝냈습니다 headerReferenceSize. 문제가 더 이상 발생하지 않고 레이아웃이 시각적으로 허용되는 값을 찾을 때까지 약 50 개의 조합을 시도했습니다. 두 화면 크기 모두에서 작동하는 값을 찾는 것은 매우 어렵습니다.

요약하자면, 값을 가지고 놀면서 다른 화면 크기에서 확인하고 Apple이이 문제를 해결하기를 바랍니다.


3

iOS 10 에서 UICollectionView 스크롤 후 셀이 사라지는 것과 비슷한 문제가 발생했습니다 (iOS 6-9에서는 문제 없음).

UICollectionViewFlowLayout의 서브 클래 싱 및 layoutAttributesForElementsInRect 메서드 재정의 : 내 경우에는 작동하지 않습니다.

해결책은 충분히 간단했습니다. 현재 UICollectionViewFlowLayout 인스턴스를 사용하고 itemSize와 expectedItemSize를 모두 설정 했습니다. 사용하지 않았 음) 0이 아닌 크기로 설정했습니다. 실제 크기는 collectionView : layout : sizeForItemAtIndexPath : 메소드에서 계산됩니다.

또한 불필요한 다시로드를 방지하기 위해 layoutSubviews에서 invalidateLayout 메서드 호출을 제거했습니다.


uicollectionviewflowlayout에서 항목 크기 및 예상 항목 크기를 어디에 설정 했습니까?
Garrett Cox

UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout 할당] init]; [flowLayout setItemSize : CGSizeMake (200, 200)]; [flowLayout setEstimatedItemSize : CGSizeMake (200, 200)]; self.collectionView = [[UICollectionView 할당] initWithFrame : CGRectZero collectionViewLayout : flowLayout];
Andrey Seredkin 2016

설정 estimatedItemSize 나를 위해 그것을했다
wmurmann

2

비슷한 문제가 발생했지만 매우 다른 해결책을 찾았습니다.

가로 스크롤이있는 UICollectionViewFlowLayout의 사용자 지정 구현을 사용하고 있습니다. 또한 각 셀에 대해 사용자 지정 프레임 위치를 만들고 있습니다.

내가 가진 문제는 [super layoutAttributesForElementsInRect : rect]가 실제로 화면에 표시되어야하는 모든 UICollectionViewLayoutAttributes를 반환하지 않는다는 것입니다. [self.collectionView reloadData]를 호출하면 일부 셀이 갑자기 숨김으로 설정됩니다.

내가 한 일은 지금까지 본 모든 UICollectionViewLayoutAttributes를 캐시 한 다음 표시되어야하는 항목을 포함하는 NSMutableDictionary를 만드는 것입니다.

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

나도 가로 스크롤을 사용하고 있으며 솔루션을 사용하여 문제를 해결할 수 있지만 다른보기로 segue를 수행하고 돌아온 후 똑같이 열로 나누지 않는 추가 항목이 있으면 콘텐츠 크기가 잘못된 것 같습니다.
morph85

segue를 수행하고 컬렉션보기로 돌아간 후 셀이 숨겨지는 문제를 해결하는 솔루션을 찾았습니다. collectionViewFlowLayout에서 expectedItemSize를 설정하지 마십시오. itemSize를 직접 설정하십시오.
morph85

2

위의 답변은 저에게 효과가 없지만 이미지를 다운로드 한 후

[self.yourCollectionView reloadData]

[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

새로 고치려면 모든 셀을 올바르게 표시 할 수 있습니다.


0

조금 늦을 수 있지만 prepare()가능하면 속성을 설정하고 있는지 확인하십시오 .

내 문제는 셀이 배치되고 layoutAttributesForElements. 이로 인해 새 셀이 표시 될 때 깜박임 효과가 발생했습니다.

모든 속성 논리를로 이동 prepare한 다음 설정 UICollectionViewCell.apply()하면 깜박임이 제거되고 버터 부드러운 셀이 표시됩니다 😊

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.