UICollectionView flowLayout không gói các ô một cách chính xác


79

Tôi có UICollectionViewmột FLowLayout. Nó sẽ hoạt động như tôi mong đợi hầu hết thời gian, nhưng thỉnh thoảng một trong các ô không quấn đúng cách. Ví dụ: ô sẽ nằm trên "cột" đầu tiên của hàng thứ ba nếu thực sự nằm sau trong hàng thứ hai và chỉ có một khoảng trống ở vị trí đó (xem sơ đồ bên dưới). Tất cả những gì bạn có thể thấy về ô rouge này là phía bên tay trái (phần còn lại bị cắt bỏ) và nơi nó sẽ trống.

Điều này không xảy ra nhất quán; nó không phải luôn luôn cùng một hàng. Khi nó đã xảy ra, tôi có thể cuộn lên rồi quay lại và ô sẽ tự sửa. Hoặc, khi tôi nhấn vào ô (đưa tôi đến chế độ xem tiếp theo thông qua một cú nhấn) và sau đó bật lại, tôi sẽ thấy ô ở vị trí không chính xác và sau đó nó sẽ nhảy đến vị trí chính xác.

Tốc độ cuộn dường như giúp việc tái tạo sự cố dễ dàng hơn. Khi tôi cuộn chậm, thỉnh thoảng tôi vẫn có thể thấy ô ở vị trí sai, nhưng sau đó nó sẽ ngay lập tức nhảy đến vị trí chính xác.

Sự cố bắt đầu xảy ra khi tôi thêm nội dung các phần. Trước đây, tôi đã có các ô gần như nằm ngang với giới hạn bộ sưu tập (ít hoặc không có nội bộ) và tôi không nhận thấy vấn đề. Nhưng điều này có nghĩa là bên phải và bên trái của chế độ xem bộ sưu tập trống. Tức là, không thể cuộn. Ngoài ra, thanh cuộn không nằm ngang bên phải.

Tôi có thể làm cho sự cố xảy ra trên cả Simulator và iPad 3.

Tôi đoán sự cố đang xảy ra do phần bên trái và bên phải được chèn vào ... Nhưng nếu giá trị sai, thì tôi mong đợi hành vi sẽ nhất quán. Tôi tự hỏi liệu đây có thể là một lỗi của Apple? Hoặc có lẽ điều này là do sự tích tụ của các tấm lót hoặc thứ gì đó tương tự.

Minh họa vấn đề và cài đặt


Theo dõi : Tôi đã sử dụng câu trả lời này dưới đây của Nick trong hơn 2 năm nay mà không có vấn đề gì (trong trường hợp mọi người tự hỏi liệu có bất kỳ lỗ hổng nào trong câu trả lời đó - tôi vẫn chưa tìm thấy bất kỳ điều gì). Làm tốt lắm Nick.

Câu trả lời:


94

Có một lỗi trong quá trình triển khai layoutAttributesForElementsInRect của UICollectionViewFlowLayout khiến nó trả về HAI đối tượng thuộc tính cho một ô trong một số trường hợp liên quan đến phần nội dung. Một trong các đối tượng thuộc tính được trả về không hợp lệ (nằm ngoài giới hạn của chế độ xem bộ sưu tập) và đối tượng còn lại là hợp lệ. Dưới đây là lớp con của UICollectionViewFlowLayout khắc phục sự cố bằng cách loại trừ các ô bên ngoài giới hạn của chế độ xem bộ sưu tập.

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

Xem này .

Các câu trả lời khác đề xuất trả lại YES từ shouldInvalidateLayoutForBoundsChange, nhưng điều này gây ra các tính toán lại không cần thiết và thậm chí không giải quyết được hoàn toàn vấn đề.

Giải pháp của tôi hoàn toàn giải quyết được lỗi và không gây ra bất kỳ sự cố nào khi Apple khắc phục nguyên nhân gốc rễ.


5
FYI lỗi này cũng xuất hiện khi cuộn ngang. Thay thế x bằng y và chiều rộng bằng chiều cao làm cho bản vá này hoạt động.
Patrick Tescher vào

Cảm ơn! Tôi chỉ mới bắt đầu chơi với collectionView (nếu bạn muốn spam Apple về điều này, đây là tài liệu tham khảo rdar openradar.appspot.com/12433891 )
Vinzzz

1
@richarddas Không, bạn không muốn kiểm tra xem các giao diện có giao nhau hay không. Trên thực tế, tất cả các ô (hợp lệ hoặc không hợp lệ) sẽ giao nhau giữa các giới hạn của chế độ xem bộ sưu tập. Bạn muốn kiểm tra xem có bất kỳ phần nào của trực tràng nằm ngoài giới hạn hay không, đó là những gì mã của tôi thực hiện.
Nick Snyder

2
@Rpranata Kể từ iOS 7.1, lỗi này vẫn chưa được sửa. Thở dài.
nonamelive

2
Có thể tôi đang sử dụng điều này sai, nhưng trên iOS 8.3 trong Swift, điều này khiến các chế độ xem phụ ở bên phải đã từng bị cắt bỏ hoàn toàn không xuất hiện. Ai khác?
sudo

8

Đặt cái này vào viewController sở hữu chế độ xem bộ sưu tập

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

Bạn đặt nó ở đâu?
fatuhoku

Vào chế độ xem Bộ điều khiển sở hữu chế độ xem bộ sưu tập
Peter Lapisu

3
Vấn đề của tôi là các tế bào đã biến mất hoàn toàn. Giải pháp này đã hữu ích - tuy nhiên điều này gây ra tải lại không cần thiết. Vẫn nó đang hoạt động bây giờ .. thx!
pawi

1
nó gây ra một vòng lặp vô hạn nếu gọi từ các viewController đối với tôi
Hofi

Theo ghi nhận của DHennessy13 , giải pháp hiện tại này là tốt nhưng có thể không hoàn hảo vì nó sẽ làm mất hiệu lựcLayout khi xoay màn hình (và đối với hầu hết các trường hợp thì không). Một cải tiến có thể là đặt cờ invalidateLayoutchỉ một lần.
Cœur

7

tôi đã phát hiện ra các vấn đề tương tự trong ứng dụng iPhone của mình. Tìm kiếm trên diễn đàn nhà phát triển của Apple đã mang lại cho tôi giải pháp phù hợp này, giải pháp phù hợp trong trường hợp của tôi và có thể cũng sẽ trong trường hợp của bạn:

Lớp con UICollectionViewFlowLayoutvà ghi đè shouldInvalidateLayoutForBoundsChangeđể trả về YES.

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

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

Điều này chỉ giải quyết một phần vấn đề tôi đang gặp phải với vấn đề này. Ô thực sự đi đến đúng vị trí khi hàng xuất hiện. Tuy nhiên, ngay trước khi nó xuất hiện, tôi vẫn nhận được một ô xuất hiện ở bên cạnh.
Daniel Wood

Cẩn thận - làm điều này sẽ khiến bố cục chạy mỗi khi bạn cuộn. Điều này có thể ảnh hưởng nghiêm trọng đến hiệu suất.
fatuhoku

7

Một phiên bản Swift của câu trả lời của 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 }
    }
}

1
điều này hoàn toàn làm cho CollectionViewCell biến mất ... Có giải pháp nào khác không?
Giggs

5

Tôi cũng gặp sự cố này đối với bố cục dạng lưới cơ bản với các tấm lót cho lề. Việc gỡ lỗi giới hạn mà tôi đã thực hiện bây giờ đang triển khai - (NSArray *)layoutAttributesForElementsInRect:(CGRect)recttrong lớp con UICollectionViewFlowLayout của tôi và bằng cách ghi lại những gì mà việc triển khai siêu lớp trả về, điều này cho thấy rõ ràng vấn đề.

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

Bằng cách triển khai, - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPathtôi cũng có thể thấy rằng nó dường như trả về các giá trị sai cho itemIndexPath.item == 30, là hệ số 10 của số ô trên mỗi dòng trong gridview của tôi, không chắc liệu điều đó có liên quan hay không.

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

Với việc thiếu thời gian để gỡ lỗi nhiều hơn, giải pháp mà tôi đã thực hiện bây giờ là giảm chiều rộng các chế độ xem bộ sưu tập của tôi với một lượng bằng với lề trái và phải. Tôi có một tiêu đề vẫn cần chiều rộng đầy đủ vì vậy tôi đã đặt clipsToBounds = NO trên chế độ xem bộ sưu tập của mình và sau đó cũng loại bỏ các nội dung bên trái và bên phải trên đó, dường như hoạt động. Để chế độ xem tiêu đề giữ nguyên vị trí, bạn cần thực hiện chuyển đổi khung và định kích thước trong các phương thức bố cục có nhiệm vụ trả về layoutAttributes cho chế độ xem tiêu đề.


Cảm ơn về thông tin bổ sung @monowerker. Tôi nghĩ rằng vấn đề của tôi bắt đầu khi tôi thêm nội dung (tôi đã thêm điều này vào câu hỏi). Tôi sẽ thử các phương pháp gỡ lỗi của bạn và xem nó có cho tôi biết điều gì không. Tôi cũng có thể thử công việc của bạn.
lindon fox

Đây rất có thể là một lỗi trong UICFL / UICL, tôi sẽ thử và gửi radar khi tôi có thời gian, đây là một cuộc thảo luận với một số rdar-number mà bạn có thể tham khảo. twitter.com/steipete/status/258323913279410177
monowerker

4

Tôi đã thêm một báo cáo lỗi cho Apple. Điều phù hợp với tôi là đặt bottom sectionInset thành giá trị nhỏ hơn top inset.


3

Tôi đã gặp phải vấn đề tương tự về lỗi di động trên iPhone bằng cách sử dụng UICollectionViewFlowLayoutvà vì vậy tôi rất vui khi tìm thấy bài đăng của bạn. Tôi biết bạn đang gặp sự cố trên iPad, nhưng tôi đăng bài này vì tôi nghĩ rằng đó là vấn đề chung với UICollectionView. Vì vậy, đây là những gì tôi phát hiện ra.

Tôi có thể xác nhận rằng sectionInsetcó liên quan đến vấn đề đó. Bên cạnh đó, headerReferenceSizecũng có ảnh hưởng đến việc một tế bào bị thay thế hay không. (Điều này có ý nghĩa vì nó cần thiết để xác định nguồn gốc.)

Thật không may, ngay cả các kích thước màn hình khác nhau cũng phải được tính đến. Khi xem xét các giá trị cho hai thuộc tính này, tôi nhận thấy rằng một cấu hình nhất định hoạt động trên cả hai (3,5 "và 4"), không có hoặc chỉ trên một trong các kích thước màn hình. Thường thì không có. (Điều này cũng có ý nghĩa, vì giới hạn của những UICollectionViewthay đổi, do đó tôi không gặp bất kỳ sự khác biệt nào giữa võng mạc và không võng mạc.)

Tôi đã kết thúc việc thiết lập sectionInsetheaderReferenceSizetùy thuộc vào kích thước màn hình. Tôi đã thử khoảng 50 kết hợp cho đến khi tôi tìm thấy các giá trị mà sự cố không xảy ra nữa và bố cục có thể chấp nhận được về mặt hình ảnh. Rất khó để tìm các giá trị hoạt động trên cả hai kích thước màn hình.

Vì vậy, tóm lại, tôi chỉ có thể khuyên bạn nên thử với các giá trị, kiểm tra các giá trị này trên các kích thước màn hình khác nhau và hy vọng rằng Apple sẽ khắc phục sự cố này.


3

Tôi vừa gặp sự cố tương tự với các ô biến mất sau khi cuộn UICollectionView trên iOS 10 (không gặp sự cố nào trên iOS 6-9).

Phân lớp của UICollectionViewFlowLayout và ghi đè phương thức layoutAttributesForElementsInRect: không hoạt động trong trường hợp của tôi.

Giải pháp đủ đơn giản. Hiện tại, tôi sử dụng một phiên bản của UICollectionViewFlowLayout và đặt cả itemSize và ước tínhItemSize (trước đây tôi không sử dụng ước lượngItemSize ) và đặt nó thành một số kích thước khác 0. Kích thước thực tế đang được tính toán trong phương thức collectionView: layout: sizeForItemAtIndexPath:.

Ngoài ra, tôi đã xóa một lệnh gọi phương thức validateLayout khỏi layoutSubviews để tránh tải lại không cần thiết.


ở đâu đã đặt kích thước mục và kích thước ước tính trong uicollectionviewflowlayout?
Garrett Cox

UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout phân bổ] init]; [flowLayout setItemSize: CGSizeMake (200, 200)]; [flowLayout setEsosystemItemSize: CGSizeMake (200, 200)]; self.collectionView = [[UICollectionView layout] initWithFrame: CGRectZero collectionViewLayout: flowLayout];
Andrey Seredkin

Thiết estimatedItemSize đã làm nó cho tôi
wmurmann

2

Tôi vừa gặp sự cố tương tự nhưng đã tìm thấy một giải pháp rất khác.

Tôi đang sử dụng triển khai tùy chỉnh của UICollectionViewFlowLayout với cuộn ngang. Tôi cũng đang tạo các vị trí khung tùy chỉnh cho từng ô.

Vấn đề mà tôi gặp phải là [super layoutAttributesForElementsInRect: trực tràng] không thực sự trả về tất cả các thuộc tính UICollectionViewLayoutAttributes sẽ được hiển thị trên màn hình. Khi gọi đến [self.collectionView reloadData], một số ô sẽ đột ngột được đặt thành ẩn.

Cuối cùng, những gì tôi đã làm là tạo một NSMutableDictionary lưu trữ tất cả các thuộc tính UICollectionViewLayoutAttributes mà tôi đã thấy cho đến nay và sau đó bao gồm bất kỳ mục nào mà tôi biết sẽ được hiển thị.

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

Đây là danh mục cho NSMutableDictionary mà UICollectionViewLayoutAttributes đang được lưu chính xác.

#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

Tôi cũng đang sử dụng tính năng cuộn ngang, tôi cố gắng khắc phục sự cố bằng giải pháp của bạn, nhưng sau khi thực hiện chuyển sang chế độ xem khác và quay lại, kích thước nội dung dường như không đúng khi có thêm các mục không chia đều thành các cột.
morph85

Tôi đã tìm thấy giải pháp để khắc phục sự cố trong đó ô bị ẩn sau khi thực hiện xác định và quay lại chế độ xem bộ sưu tập. Cố gắng không đặt Kích thước ước tính trong collectionViewFlowLayout; đặt mục Kích thước trực tiếp.
morph85

2

Các câu trả lời trên không phù hợp với tôi, nhưng sau khi tải xuống hình ảnh, tôi đã thay thế

[self.yourCollectionView reloadData]

với

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

để làm mới và nó có thể hiển thị tất cả các ô một cách chính xác, bạn có thể thử.


0

Điều này có thể hơi muộn nhưng hãy đảm bảo rằng bạn đang thiết lập các thuộc tính của mình trong prepare() nếu có thể.

Vấn đề của tôi là các ô được bố trí, sau đó nhận được bản cập nhật layoutAttributesForElements . Điều này dẫn đến hiệu ứng nhấp nháy khi các ô mới xuất hiện.

Bằng cách chuyển tất cả logic thuộc tính vào prepare, sau đó đặt chúng trong UICollectionViewCell.apply()đó đã loại bỏ hiện tượng nhấp nháy và tạo ra ô mịn bơ hiển thị 😊

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.