There are two different uses for the
shadowPath property on
- Improving the performance of having a shadow
- Creating shadows that don’t match the contents of the view. Check out Apple’s Using Shadow Path for Special Effects.
For performance reasons, always set a
shadowPath. This is a substantial improvement, especially if the view changes position via animation or presence in a scroll view.
When you can set a path
shadowPath tells the system what should be casting a shadow without having to look at the contents of the view itself. Since most views that need a shadow are opaque, we just need to describe the appearance of the background of the view.
Using the convenience initializers on
UIBezierPath we can create ovals, squares and rounded rectangles without difficulty. For more complicated paths, check out A Primer on Bézier Curves. You can still use
CGPath to create them, but it will require more complicated math.
Starting with a simple, purple view with a shadow:
let purpleView = UIView() purpleView.backgroundColor = .purple purpleView.layer.shadowRadius = 10.0 purpleView.layer.shadowColor = UIColor.black.cgColor purpleView.layer.shadowOffset = CGSize() purpleView.layer.shadowOpacity = 0.8
We can tell the system to draw a shadow for the entire square:
purpleView.layer.shadowPath = UIBezierPath(rect: purpleView.bounds).cgPath
For rounded corners, we can set the
cornerRadius property on the layer, and create a matching
purpleView.layer.cornerRadius = 16.0 purpleView.layer.shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 16.0).cgPath
When you can’t set a path
Sometimes it’s not possible to set a path because there’s no easy way to describe the contents of the view. For example, text is a mess of random contents. Rasterizing the layer avoids having to draw the shadow repeatedly.
// create our label let label = UILabel() label.textColor = .purple label.text = NSLocalizedString("Swift Lemma!", comment: "") label.layer.shadowOpacity = 0.6 label.layer.shadowColor = UIColor.black.cgColor label.layer.shadowOffset = CGSize(width: 0, height: 2) // render and cache the layer label.layer.shouldRasterize = true // make sure the cache is retina (the default is 1.0) label.layer.rasterizationScale = UIScreen.main.scale
This produces a view that looks like this:
Keep in mind
Always set the
shadowPath inside either
viewDidLayoutSubviews(). Since Auto Layout likely means there aren’t constant sizes for views, setting a
shadowPath elsewhere may become outdated or incorrect.
When creating a path, the coordinate system for the path is the layer it’s applied to. To make it easier, pretend the shadow path is a subview. For this reason, we use the bounds of the view to create its shadow path.