iOS13自定义UICollectionViewFlowLayout添加阴影装饰视图崩溃求解
崩溃触发原因
- 你的自定义布局没有实现UICollectionViewFlowLayout要求的公开方法
layoutAttributesForDecorationView(ofKind:at:),现有代码中写的是自定义的私有同名方法,参数为section,不符合系统方法签名。 - iOS13系统在执行UICollectionView批量更新(你项目中由IGListKit触发)时,会主动调用这个系统公开方法获取装饰视图的布局属性,找不到对应实现时就会抛出
NSInternalInconsistencyException断言。iOS14及之后的系统优化了布局属性读取逻辑,会优先从layoutAttributesForElements返回的属性列表中查找,所以高版本系统不会触发崩溃。 - 额外触发场景:当数据源发生变化(比如section被删除)时,系统布局缓存中还保留了旧的装饰视图索引路径,此时如果方法返回nil也会触发系统断言。
修复方案
按照系统规范重写装饰视图布局属性获取方法,同时兼容边界情况避免返回nil:
第一步:重写系统公开的装饰视图属性方法
替换你代码中私有的layoutAttributesForDecorationView(ofKind:at:)方法,改成重写系统的公开方法:
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { guard elementKind == decorationViewKind else { return super.layoutAttributesForDecorationView(ofKind: elementKind, at: indexPath) } let section = indexPath.section // 先校验section有效性、是否需要显示阴影 guard let collectionView = collectionView, section < collectionView.numberOfSections, (decorationDelegate?.shouldDisplayShadowFor(section: section) ?? true) else { // 不需要显示时也不要返回nil,返回隐藏的空属性避免系统断言 let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) attributes.frame = .zero attributes.isHidden = true return attributes } let numberOfItems = collectionView.numberOfItems(inSection: section) guard numberOfItems > 0 else { let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) attributes.frame = .zero attributes.isHidden = true return attributes } // 原有计算阴影frame的逻辑保留 let firstItemIndexPath = IndexPath(item: 0, section: section) guard let firstItemFrame = layoutAttributesForItem(at: firstItemIndexPath)?.frame else { let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) attributes.frame = .zero attributes.isHidden = true return attributes } let lastItemIndexPath = IndexPath(item: numberOfItems - 1, section: section) guard let lastItemFrame = layoutAttributesForItem(at: lastItemIndexPath)?.frame else { let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) attributes.frame = .zero attributes.isHidden = true return attributes } let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) attributes.frame = CGRect( x: firstItemFrame.origin.x, y: firstItemFrame.origin.y, width: firstItemFrame.width, height: lastItemFrame.maxY - firstItemFrame.minY ) attributes.zIndex = -1 attributes.isHidden = false return attributes }
第二步:修改layoutAttributesForElements中的调用逻辑
调整原来调用自定义私有方法的代码,适配新的方法签名:
let backgroundShadowAttributes: [UICollectionViewLayoutAttributes] = sectionsWithShadow.compactMap{ section in // 传入section对应item=0的indexPath即可 return layoutAttributesForDecorationView( ofKind: decorationViewKind, at: IndexPath(item: 0, section: section) ) }.filter{ !$0.isHidden } // 过滤掉隐藏的属性避免多余渲染
修改后即可兼容iOS13及以上所有系统版本,不会再触发该崩溃。
内容的提问来源于stack exchange,提问作者Tometoyou
相关产品推荐
相关产品推荐

