29

我在 CollectionView 中使用 CustomLayout 完成了一个视图。在 iOS6 中它运行良好,但在 iOS7 中它会抛出这样的异常。

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:

'索引路径 ({length = 2, path = 0 - 0}) 的补充项目的布局属性从 CustomSupplementaryAttributes: 0xd1123a0 索引路径: (NSIndexPath: 0xd112580 {length = 2, path = 0 - 0}) 更改;元素种类:(标识符);帧 = (0 0; 1135.66 45); zIndex = -1; to CustomSupplementaryAttributes: 0xd583c80 index path: (NSIndexPath: 0xd583c70 {length = 2, path = 0 - 0}); 元素种类:(标识符);帧 = (0 0; 1135.66 45); zIndex = -1; 在不使布局无效的情况下'

4

7 回答 7

63

iOS 10

在 iOS 10 中,引入了一项新功能,即Cell Prefetching. 它会让SupplementaryView崩溃的动态位置。为了以旧行为运行,它需要禁用prefetchingEnabled. 默认情况下,它true在 iOS 10 中。

// Obj-C
// This function is available in iOS 10. Disable it for dynamic position of `SupplementaryView `.
if ([self.collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) {
    self.collectionView.prefetchingEnabled = false;
}

// Swift
if #available(iOS 10, *) { 
    // Thanks @maksa
    collectionView.prefetchingEnabled = false 

    // Swift 3 style
    colView.isPrefetchingEnabled = false   
}

我讨厌这个问题。我花了 2 天时间解决这个问题。关于Cell Pre-fetch @iOS 10的参考。


iOS 9 及之前...

@Away Lin 是对的。. 我通过实现该委托方法解决了同样的问题。

Custom UICollectionViewLayout将修改layoutAttributesForElementsInRect. 截面位置是动态的,而不是静态的。因此,我收到有关layout attributes for supplementary item at index path ... changed from ... to .... 在更改之前,invalideLayout应调用相关方法。

并且,在实现了这个return的委托方法之后,在滚动的时候会调用true该方法。默认情况下,它返回.invalidateLayoutWithContext:UICollectionViewLayoutfalse

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

来自 Apple 文档

返回值 如果集合视图需要更新布局,则返回 true;如果不需要更改布局,则返回 false。

讨论 此方法的默认实现返回 false。子类可以覆盖它并根据集合视图边界的更改是否需要更改单元格和补充视图的布局来返回适当的值。

如果集合视图的边界发生变化并且此方法返回 true,则集合视图通过调用 invalidateLayoutWithContext: 方法使布局无效。

可用性 适用于 iOS 6.0 及更高版本。


和更多 ...

GitHub 上一个不错的示例项目,用于自定义 UICollectionViewLayout。

于 2016-06-01T08:29:22.967 回答
18

您需要在更新之前使现有布局无效,请参阅错误消息的末尾:

在不使布局无效的情况下'

[collectionViewLayout invalidateLayout];

UICollectionViewLayout 的 Apple 文档

于 2013-10-06T11:29:25.237 回答
11

我有同样的例外:在 iOS 7 中,您现在需要覆盖isEqual:在您的 UICollectionViewLayoutAttributes 子类中继承的,如此处 Apple 文档中所述

于 2013-10-09T22:31:00.017 回答
5

我通过覆盖以下子类的方法解决了我的问题UICollectionViewFlowLayout

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound

返回是

于 2015-11-30T06:58:26.223 回答
2

我不完全确定如何或为什么,但这似乎已在 iOS 12 中修复,支持补充视图调整大小和预取。对我来说,诀窍是确保事情以正确的顺序发生。

这是可拉伸标题视图的工作实现。注意标题调整大小的实现发生在layoutAttributesForElements(in rect: CGRect)

class StretchyHeaderLayout: UICollectionViewFlowLayout {

    var cache = [UICollectionViewLayoutAttributes]()

    override func prepare() {
        super.prepare()

        cache.removeAll()

        guard let collectionView = collectionView else { return }

        let sections = [Int](0..<collectionView.numberOfSections)
        for section in sections {
            let items = [Int](0..<collectionView.numberOfItems(inSection: section))
            for item in items {
                let indexPath = IndexPath(item: item, section: section)
                if let attribute = layoutAttributesForItem(at: indexPath) {
                    cache.append(attribute)
                }
            }
        }

        if let header = layoutAttributesForSupplementaryView(ofKind: StretchyCollectionHeaderKind, at: IndexPath(item: 0, section: 0)) {
            cache.append(header)
        }
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        let visibleAttributes = cache.filter { rect.contains($0.frame) || rect.intersects($0.frame) }

        guard let collectionView = collectionView else { return visibleAttributes }

        // Find the header and stretch it while scrolling.
        guard let header = visibleAttributes.filter({ $0.representedElementKind == StretchyCollectionHeaderKind }).first else { return visibleAttributes }
        header.frame.origin.y = collectionView.contentOffset.y
        header.frame.size.height = headerHeight.home - collectionView.contentOffset.y
        header.frame.size.width = collectionView.frame.size.width

        return visibleAttributes
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as! UICollectionViewLayoutAttributes
        guard collectionView != nil else { return attributes }

        attributes.frame.origin.y =  headerHeight.home + attributes.frame.origin.y

        return attributes
    }

    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, with: indexPath)
    }

    override var collectionViewContentSize: CGSize {
        get {
            guard let collectionView = collectionView else { return .zero }

            let numberOfSections = collectionView.numberOfSections
            let lastSection = numberOfSections - 1
            let numberOfItems = collectionView.numberOfItems(inSection: lastSection)
            let lastItem = numberOfItems - 1

            guard let lastCell = layoutAttributesForItem(at: IndexPath(item: lastItem, section: lastSection)) else { return .zero }

            return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom)
        }
    }
}

PS:我知道缓存在这一点上实际上没有任何用途:)

于 2018-09-28T22:52:23.910 回答
1

我也遇到了这个问题,因为我的代码取决于集合视图的内容大小。我的代码是通过collectionView!.contentSize而不是访问内容大小collectionViewContentSize

前者使用 的collectionView属性UICollectionViewLayout,而后者使用自定义实现的布局属性。在我的代码中,第一次要求布局提供属性时,contentSize尚未设置。

于 2017-01-02T23:42:32.063 回答
0

选择 CollectionView 和 Goto 属性检查器。取消选中启用预取的复选框。这是解决我的问题。截屏

于 2019-12-27T06:24:33.513 回答