在UICollectionView布局中自定义标头的位置会导致NSInternalInconsistencyException错误

Ken*_*eld 11 ios uicollectionview uicollectionviewlayout ios7

我试图在UICollectionView使用子UICollectionViewFlowLayout类的类中自定义标题的位置(基于堆叠标题的代码松散地显示在此处输入链接描述).

作为最小测试,假设我只想在所有标题的位置添加固定偏移量:

  • 我将所有标题添加到返回的数组中,layoutAttributesForElementsInRect以便始终处理所有标题(这可能是问题的原因,我不确定)
  • 然后我通过添加固定的偏移来更新每个标头 layoutAttributesForSupplementaryViewOfKind

完整的实施包含在本文末尾.

(顺便说一下,我知道在第一步中添加所有标题,包括那些在rect之外的标题,并不是严格说来,但这是一个简单的例子,说明我希望制作的位置更复杂,这会导致所有标题在绘制矩形中显示.)

但是,当我运行代码时,我得到以下内容NSInternalInconsistencyException:

2014-01-15 00:41:50.130 CollectionStackedHeaders[60777:70b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'layout attributes for supplementary item at index path (<NSIndexPath: 0x8a7db90> {length = 2, path = 0 - 0})
changed from <UICollectionViewLayoutAttributes: 0x8a7f8b0> index path: (<NSIndexPath: 0x8a7d9c0> {length = 2, path = 0 - 0}); element kind: (UICollectionElementKindSectionHeader); frame = (0 0; 320 50);
        to   <UICollectionViewLayoutAttributes: 0x8a7fb80> index path: (<NSIndexPath: 0x8a7db90> {length = 2, path = 0 - 0}); element kind: (UICollectionElementKindSectionHeader); frame = (0 50; 320 50); zIndex = 1024;
without invalidating the layout'
Run Code Online (Sandbox Code Playgroud)

这似乎是由更新属性引起的,就像我注释掉以下两行一样,它工作正常:

attributes.zIndex = 1024;
attributes.frame = frame;
Run Code Online (Sandbox Code Playgroud)

是什么导致了这个错误,我该怎么做才能让我的简单示例启动并运行?

以下是此简单示例的完整类实现:

@implementation myStackedHeaderFlowLayout

- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    // Call super to get elements
    NSMutableArray* answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];

    // As a test, always add first header to the answer array
    NSArray* indexes = [NSArray arrayWithObjects: [NSNumber numberWithInt:0], nil];
    for (NSNumber* sectionNumber in indexes) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:[sectionNumber integerValue]];
        UICollectionViewLayoutAttributes* layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
        if (layoutAttributes) {
            [answer removeObject:layoutAttributes]; // remove if already present
            [answer addObject:layoutAttributes];
        }
    }

    return answer;
}

- (UICollectionViewLayoutAttributes*)layoutAttributesForSupplementaryViewOfKind:(NSString*)kind atIndexPath:(NSIndexPath*)indexPath {
    // Call super to get base attributes
    UICollectionViewLayoutAttributes* attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];

    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        CGRect frame = attributes.frame;
        frame.origin.y += 50;
        // Update attributes position here - causes the problem
        attributes.zIndex = 1024;
        attributes.frame = frame;
    }

    return attributes;
}

- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString*)kind atIndexPath:(NSIndexPath*)indexPath {
    UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
    return attributes;
}

- (UICollectionViewLayoutAttributes*)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString*)kind atIndexPath:(NSIndexPath*)indexPath {
    UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
    return attributes;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
    return YES;
}

@end
Run Code Online (Sandbox Code Playgroud)

tit*_*coy 16

layout attributes for supplementary item at index path (<NSIndexPath>) 
changed from <UICollectionViewLayoutAttributes> 
to <UICollectionViewLayoutAttributes>
without invalidating the layout
Run Code Online (Sandbox Code Playgroud)

根据我的经验,NSInternalInconsistencyException当返回的数组layoutAttributesForElementsInRect:包含UICollectionViewLayoutAttributes具有相同索引路径和(补充)元素类别的两个对象时,抛出上面的描述.


Ken*_*eld 1

好吧,我不是 100% 确定为什么,但layoutAttributesForElementsInRect用以下内容替换似乎可以解决问题:

- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    // Call super to get elements
    NSMutableArray* answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];

    NSUInteger maxSectionIndex = 0;
    for (NSUInteger idx=0; idx < [answer count]; ++idx) {
        UICollectionViewLayoutAttributes *layoutAttributes = answer[idx];

        if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell || layoutAttributes.representedElementCategory == UICollectionElementCategorySupplementaryView) {
            // Keep track of the largest section index found in the rect (maxSectionIndex)
            NSUInteger sectionIndex = (NSUInteger)layoutAttributes.indexPath.section;
            if (sectionIndex > maxSectionIndex) {
                maxSectionIndex = sectionIndex;
            }
        }
        if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
            // Remove layout of header done by our super, as we will do it right later
            [answer removeObjectAtIndex:idx];
            idx--;
        }
    }

    // Re-add all section headers for sections >= maxSectionIndex
    for (NSUInteger idx=0; idx <= maxSectionIndex; ++idx) {
        NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
        UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
        if (layoutAttributes) {
            [answer addObject:layoutAttributes];
        }
    }

    return answer;
}
Run Code Online (Sandbox Code Playgroud)

我只能想象 before 是在layoutAttributesForElementsInRect我添加到第一部分的控件的标头正确初始化之前被调用的,因此以编程方式确定存在哪些标头可以避免这种情况?任何想法都是受欢迎的,但有了上述问题就解决了。