VButko

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:

UICollectionViewCell with a shadow

If you only set the shadowPath on init(), 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):

Incorrect shadow

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!