【问题标题】:UICollectionView exception in UICollectionViewLayoutAttributes from iOS7iOS7 的 UICollectionViewLayoutAttributes 中的 UICollectionView 异常
【发布时间】:2013-10-13 00:40:20
【问题描述】:

我在 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 索引路径: (NSIndexPath: 0xd583c70 {length = 2, path = 0 - 0});元素种类:(标识符);帧 = (0 0; 1135.66 45); zIndex = -1;不使布局无效'

【问题讨论】:

    标签: uicollectionview uicollectionviewlayout


    【解决方案1】:

    iOS 10

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

    // 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 is right.。我通过实现该委托方法解决了同样的问题。

    我的Custom UICollectionViewLayout 将修改layoutAttributesForElementsInRect 中的属性。截面位置是动态的,而不是静态的。所以,我收到了关于layout attributes for supplementary item at index path ... changed from ... to ... 的警告。修改前需要调用invalideLayout相关方法。

    并且,在实现这个委托方法返回true之后,滚动UICollectionViewLayout时会调用方法invalidateLayoutWithContext:。默认返回false

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

    From Apple Docs

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

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

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

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


    还有更多...

    A nice example project on GitHub, for custom UICollectionViewLayout.

    【讨论】:

    • 我会给你所有关于 iOS10 的支持,这已经困扰了我一个星期。要尝试增加一些价值,这里是 Swift 解决方案:if #available(iOS 10, *) { collectionView.prefetchingEnabled = false }
    • 非常感谢您对 ios 10 的更新。我从早上开始就一直在尝试,发现您的答案帮助我在一分钟内解决了问题。我投了赞成票。
    • 这是问题的正确答案。除非您禁用预取,否则使项目(单元格)无效会引发异常。
    【解决方案2】:

    更新前需要使现有布局失效,见报错文末:

    不使布局无效'

    [collectionViewLayout invalidateLayout];
    

    Apple Documentation for UICollectionViewLayout

    【讨论】:

    • 是的,我已经覆盖了那个方法。但还是一样。最后我解决了这个问题。我所做的是每当更改collectionview 框架时,我都会为该collectionview 执行invalidateLayout。
    • 如果有帮助,您能否点赞这个问题或将其标记为正确?由于使布局无效是解决方案,您没有发布任何代码
    • 显示如何使布局无效的代码实际上很有用,而不仅仅是链接到文档
    • 但是什么时候应该使布局无效呢? “更新前”什么?在我的情况下,当旋转到横向时会发生此异常。
    • 这个答案应该澄清“更新之前”。 (在更新 CollectionView 中的内容之前?在视图使用首选属性更新其布局属性之前?
    【解决方案3】:

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

    【讨论】:

    • 这应该是正确的答案。这解决了我的问题!
    【解决方案4】:

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

    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
    

    返回是

    【讨论】:

    • 请补充一些细节来回答!
    • 不知道为什么这个答案被否决了。这正是对我有用的!谢谢!
    【解决方案5】:

    我不完全确定如何或为什么,但这似乎已在 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)
            }
        }
    }
    

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        选择 CollectionView 和 Goto 属性检查器。取消选中 Prefetching Enabled 复选框。这是解决我的问题。 Screenshot

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-10-29
          • 2014-10-02
          • 1970-01-01
          • 2013-08-15
          • 1970-01-01
          • 2014-01-26
          • 1970-01-01
          相关资源
          最近更新 更多