I've developed a custom CollectionViewLayout which uses DecorationView
to show shadows behind the cells.
However, I'd like to add this decoration only to some cells. The UICollectionView
is vertical
, but it may contain an embedded horizontal
UICollectionView
inside the cell. The cells with an embedded UICollectionView
should not be decorated, as shown on the image:
Here is the code I'm using to add a shadow. The UICollectionViewLayout
does not provide a method how to retrieve a cell's class, so it could decide whether to add a shadow or not:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let parent = super.layoutAttributesForElements(in: rect)
guard let attributes = parent, !attributes.isEmpty else {
return parent
}
let sections = attributes.map{$0.indexPath.section}
let unique = Array(Set(sections))
// Need to detect, which sections contain an embedded UICollectionView and exclude them from the UNIQUE set
let backgroundShadowAttributes: [UICollectionViewLayoutAttributes] = unique.compactMap{ section in
let indexPath = IndexPath(item: 0, section: section)
return self.layoutAttributesForDecorationView(ofKind: backgroundViewClass.reuseIdentifier(),
at: indexPath)
}
return attributes + backgroundShadowAttributes + separators
}
Is there any way to conditionally specify, which views should be decorated?
Finished with this code: A protocol to directly ask the DataSource, whether to show a shadow for a particular section:
protocol SectionBackgroundFlowLayoutDataSource {
func shouldDisplayBackgroundFor(section: Int) -> Bool
}
And leverage the protocol in the func layoutAttributesForElements(in rect: CGRect)
method:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let parent = super.layoutAttributesForElements(in: rect)
guard let attributes = parent, !attributes.isEmpty else {
return parent
}
attributes.forEach(configureRoundCornersAttributes)
// Display shadows for every section by default
var sectionsWithShadow = Set(attributes.map{$0.indexPath.section})
if let dataSource = collectionView?.dataSource as? SectionBackgroundFlowLayoutDataSource {
// Ask DataSource for sections with shadows, if it supports the protocol
sectionsWithShadow = sectionsWithShadow.filter{dataSource.shouldDisplayBackgroundFor(section: $0)}
}
let backgroundShadowAttributes: [UICollectionViewLayoutAttributes] = sectionsWithShadow.compactMap{ section in
let indexPath = IndexPath(item: 0, section: section)
return self.layoutAttributesForDecorationView(ofKind: backgroundViewClass.reuseIdentifier(),
at: indexPath)
}
return attributes + backgroundShadowAttributes + separators
}
func shouldDisplayBackgroundFor(section: Int) -> Bool
may return faster, than cellForItemAtIndexPath
, as it doesn't require full cell configuration.