自定义UICollectionViewLayout如何仅为非内嵌横向CV的Cell加DecorationView
解决自定义CollectionViewLayout中部分Cell不显示DecorationView阴影的问题
这个需求很常见——我们用DecorationView给Cell加阴影,但遇到嵌套横向CollectionView的Cell时要跳过。确实,UICollectionViewLayout本身没法直接拿到Cell实例,不过我们可以通过数据源标记、indexPath规则或者自定义布局属性这几种方式来区分需要阴影的Cell,下面给你具体的实现方案:
方案一:通过数据模型标记区分(最灵活)
给你的数据模型加一个布尔属性,用来标记当前Item是否需要阴影装饰。这样Layout可以通过数据源拿到对应的数据,判断是否生成DecorationView。
步骤1:更新数据模型
struct YourDataModel { // 你的原有数据字段 let title: String // 添加标记字段 let needsShadowDecoration: Bool }
步骤2:在自定义Layout中判断
重写Layout的相关方法,获取对应位置的数据,根据标记决定是否返回DecorationView的布局属性:
class CustomShadowLayout: UICollectionViewFlowLayout { // 注册DecorationView override init() { super.init() register(ShadowDecorationView.self, forDecorationViewOfKind: "ShadowDecoration") } required init?(coder: NSCoder) { super.init(coder: coder) register(ShadowDecorationView.self, forDecorationViewOfKind: "ShadowDecoration") } // 返回所有元素的布局属性(包括DecorationView) override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let originalAttributes = super.layoutAttributesForElements(in: rect), let collectionView = collectionView, let dataSource = collectionView.dataSource as? YourDataSourceType else { return originalAttributes } var allAttributes = originalAttributes for itemAttributes in originalAttributes where itemAttributes.representedElementCategory == .cell { let indexPath = itemAttributes.indexPath let dataModel = dataSource.dataArray[indexPath.item] // 如果当前Item不需要阴影,跳过 guard dataModel.needsShadowDecoration else { continue } // 创建DecorationView的布局属性 let decorationAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: "ShadowDecoration", with: indexPath) decorationAttributes.frame = itemAttributes.frame.insetBy(dx: -8, dy: -8) // 调整阴影范围 decorationAttributes.zIndex = itemAttributes.zIndex - 1 // 放在Cell下方 allAttributes.append(decorationAttributes) } return allAttributes } }
方案二:通过indexPath规则区分(适合布局固定的场景)
如果嵌套横向CV的Cell都集中在特定的section或row,直接根据indexPath判断即可,不需要修改数据模型:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let originalAttributes = super.layoutAttributesForElements(in: rect) else { return originalAttributes } var allAttributes = originalAttributes for itemAttributes in originalAttributes where itemAttributes.representedElementCategory == .cell { let indexPath = itemAttributes.indexPath // 假设嵌套横向CV的Cell在section 1,跳过这些Cell的阴影 if indexPath.section == 1 { continue } // 创建并添加DecorationView属性,代码同方案一 let decorationAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: "ShadowDecoration", with: indexPath) decorationAttributes.frame = itemAttributes.frame.insetBy(dx: -8, dy: -8) decorationAttributes.zIndex = itemAttributes.zIndex - 1 allAttributes.append(decorationAttributes) } return allAttributes }
方案三:自定义布局属性传递标记
如果不想依赖数据源或indexPath,可以自定义UICollectionViewLayoutAttributes,给它加一个shouldShowShadow属性,然后在数据源或Cell中设置这个属性,Layout再根据它判断:
步骤1:自定义布局属性
class CustomLayoutAttributes: UICollectionViewLayoutAttributes { var shouldShowShadow: Bool = true override func copy(with zone: NSZone? = nil) -> Any { let copy = super.copy(with: zone) as! CustomLayoutAttributes copy.shouldShowShadow = shouldShowShadow return copy } override func isEqual(_ object: Any?) -> Bool { guard let other = object as? CustomLayoutAttributes else { return false } if other.shouldShowShadow != shouldShowShadow { return false } return super.isEqual(object) } }
步骤2:在Layout中使用自定义属性
class CustomShadowLayout: UICollectionViewFlowLayout { override class var layoutAttributesClass: AnyClass { return CustomLayoutAttributes.self } // ... 注册DecorationView等代码 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let originalAttributes = super.layoutAttributesForElements(in: rect) as? [CustomLayoutAttributes] else { return super.layoutAttributesForElements(in: rect) } var allAttributes = originalAttributes for itemAttributes in originalAttributes where itemAttributes.representedElementCategory == .cell { guard itemAttributes.shouldShowShadow else { continue } // 创建并添加DecorationView属性,代码同方案一 let decorationAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: "ShadowDecoration", with: itemAttributes.indexPath) decorationAttributes.frame = itemAttributes.frame.insetBy(dx: -8, dy: -8) decorationAttributes.zIndex = itemAttributes.zIndex - 1 allAttributes.append(decorationAttributes) } return allAttributes } }
步骤3:在数据源中设置标记
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard let attributes = collectionView.layoutAttributesForItem(at: indexPath) as? CustomLayoutAttributes else { return } // 判断当前Cell是否是嵌套横向CV的Cell,设置shouldShowShadow attributes.shouldShowShadow = !(cell is NestedHorizontalCVCell) // 刷新布局属性 collectionView.collectionViewLayout.invalidateLayout() }
注意事项
- 当数据源变化或者Cell类型变化时,记得调用
collectionView.collectionViewLayout.invalidateLayout()触发布局刷新,确保DecorationView正确更新。 - DecorationView的zIndex一定要设置为Cell的zIndex减1,这样才会显示在Cell后方。
- 调整DecorationView的frame时,要留出足够的空间显示阴影,避免被CollectionView的边界裁剪。
内容的提问来源于stack exchange,提问作者Richard Topchii
相关产品推荐
相关产品推荐

