How to add a shadow to UICollectionViewCell with Compositional Layout?
Using a shadow for UICollectionView
cells is a very common practice. Because collection view cells can change their size based on the content, it's important to apply and update shadow properly so that the shadow will be the same as the size of the cell.
As an example, we will use a collection view that uses UICollectionViewCompositionalLayout
with an estimated cell height.
Configure shadow
First of all, we need a custom UICollectionViewCell
that will have a rounded shape with a shadow:
class StatsCollectionViewCell: UICollectionViewCell {
private var cornerRadius: CGFloat = 14.0
override init(frame: CGRect) {
super.init(frame: frame)
configureLayout()
configureShadow()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
We can configure shadow in many different ways using the provided API:
private func configureShadow() {
// How blurred the shadow should be
layer.shadowRadius = 2
// How far the shadow is offset from the cell's frame
layer.shadowOffset = CGSize(width: 0, height: 2)
// The transparency of the shadow. Ranging from 0.0 (transparent) to 1.0 (opaque).
layer.shadowOpacity = 0.25
// The default color is black
layer.shadowColor = UIColor.black.cgColor
// To avoid the shadow to be clipped to the corner radius
layer.cornerRaduis = cornerRadius
layer.masksToBounds = false
}
For a better look, let's set the cell corner radius on init()
:
private func configureLayout() {
contentView.layer.cornerRadius = cornerRadius
contentView.layer.masksToBounds = true
contentView.backgroundColor = .secondarySystemGroupedBackground
}
Update shadowPath
Finally, we should specify the shadowPath
to improve performance and update the size of the shadow if the cell bounds have changed. Shadow path should be updated in layoutSubviews()
:
override func layoutSubviews() {
super.layoutSubviews()
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
}
Final result
In the end, I would like to show you the final result of our rounded cell with a shadow and visualize a problem that we can run into if we only set the shadowPath
on init()
.
When constructing the NSCollectionLayoutSection
we should use an estimated value for the height/width dimension in order to allow cells to be self-sized in that dimension. This way, the final size of the cell will be calculated when the content is rendered, so, it's important to update the shadowPath
in layoutSubviews()
as was shown before.
Here is the final result:
If you only set the
shadowPath
oninit()
, the shadow will use a height dimension that we provided as an estimated and won't be updated to the real content size (the shadow below is red for a better illustration):
Section Layout code sample
Here is a sample code of the section layout that I used in the example:
func createTotalCasesSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(200))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(200))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16)
return section
}
Conclusion
In this article, you've seen how to make a collection view cell with rounded corners and a shadow. Providing shadowPath
is very important for performance optimization, however, you should use it properly for self-sizing cells in order to avoid unexpected results.
I hope you found this article useful and interesting, and if you did, feel free to share it with a friend or on social media. If you have any questions, suggestions, or feedback, please let me know on Twitter.
Thanks for reading!