37

QuartzCore .layer.shadow's suck up performance. They appear to need to be re-rendered every time something changes, causing everything to lag.

Coregraphics gradient (for 1 way shadows) - doesn't look right. if your gradient goes from 0.3 alpha to 0, it has some odd effect where you can 'see' it stop. It just doesn't look nice, or natural. Maybe it isn't dithered, but I'm sure I heard core graphics gradients are. It's odd, I don't know.

Coregraphics shadow - take a while to render as you set them, but otherwise great performance. It's just that second you're waiting for a view to appear because it has to render it's shadow first, that's the problem.

So I must be missing something. Is there another method which looks right, and is speedy both in rendering time and in performance?

Kurt Revis
  • 27,695
  • 5
  • 68
  • 74
Andrew
  • 15,935
  • 28
  • 121
  • 203

2 Answers2

106

Adding a shadowPath should give you a huge performance boost. The following example assumes you only want the shadow on the sides of your view

CGPathRef path = [UIBezierPath bezierPathWithRect:view.bounds].CGPath;
[view.layer setShadowPath:path];

EDIT: On default a CALayer draws a shadow during animations, the following code allows you to cache the shadow as a bitmap and reuse it instead of redrawing it:

self.view.layer.shouldRasterize = YES;
// Don't forget the rasterization scale
// I spent days trying to figure out why retina display assets weren't working as expected
self.view.layer.rasterizationScale = [UIScreen mainScreen].scale;
aryaxt
  • 76,198
  • 92
  • 293
  • 442
  • 1
    rasterizationScale caught me out for a while too! You would imagine it would default based on the screen scale, but no. :-/ – Dermot Sep 16 '12 at 11:04
  • I had a partially-transparent PNG with jagged edges that I had to animate (with a shadow, of course). It was 2/3 of the screen in size, so quite a few pixels. This solution still worked flawlessly. :) – toblerpwn Oct 20 '12 at 01:53
  • Don't forget to set `rasterizationScale` to main screen's bounds, otherwise the resulting BMP will be the non-Retina view stretched up (which is ugly)... – mrcendre Jul 01 '13 at 13:48
  • **EDIT**: I meant that this was the solution that worked for me, whereas `shadowPath` has no effect on the performances for me. – mrcendre Jul 01 '13 at 17:24
  • You're a genius. @chklbumper are you using an older iPhone for testing? The iPhone 5 renders `shadowOffset` shadows quickly, but the iPhone 4 lags and glitches through the drawing the `shadowOffset` shadow. – Nate Symer Jul 20 '13 at 04:28
  • @ikzjfr0 because instead of drawing shadow below the entire view, it draws it around the view. Less shadow to re-draw during animations – aryaxt Oct 24 '14 at 05:51
  • `rasterizationScale` can bring to you better performance but not the best. i'm using it in my custom keyboard and it still get little bit laggy at the beginning. – TomSawyer Oct 17 '15 at 10:11
10

I've often seen people using the HUGE performance impact view's layer to create a rounded corner or dropshadow. Something like this:

[v.layer setCornerRadius:30.0f];
[v.layer setBorderColor:[UIColor lightGrayColor].CGColor];
[v.layer setBorderWidth:1.5f];
[v.layer setShadowColor:[UIColor blackColor].CGColor];
[v.layer setShadowOpacity:0.8];
[v.layer setShadowRadius:3.0];
[v.layer setShadowOffset:CGSizeMake(2.0, 2.0)];
.....

This has a HUGE performance impact, especially with the shadow. Putting views like this in a UITableView (or matter fact anything that moves) will create an android-ish scrolling experience, you do not want that. If you need to animate or move the view, avoid creating rounded corners or drop shadows like this by any means!

Meet Core Graphics
I've created a simple UIView subclass to show you how to achieve the same result in a slightly different way. It uses Core Graphics to draw the view and in contrast to the code above, it does not impact the performance.

Here's the drawing code:

- (void)drawRect:(CGRect)rect
{
   CGContextRef ref = UIGraphicsGetCurrentContext();

  /* We can only draw inside our view, so we need to inset the actual 'rounded content' */
  CGRect contentRect = CGRectInset(rect, _shadowRadius, _shadowRadius);

  /* Create the rounded path and fill it */
  UIBezierPath *roundedPath = [UIBezierPath bezierPathWithRoundedRect:contentRect cornerRadius:_cornerRadius];
  CGContextSetFillColorWithColor(ref, _fillColor.CGColor);
  CGContextSetShadowWithColor(ref, CGSizeMake(0.0, 0.0), _shadowRadius, _shadowColor.CGColor);
  [roundedPath fill];

  /* Draw a subtle white line at the top of the view */
  [roundedPath addClip];
  CGContextSetStrokeColorWithColor(ref, [UIColor colorWithWhite:1.0 alpha:0.6].CGColor);
  CGContextSetBlendMode(ref, kCGBlendModeOverlay);

  CGContextMoveToPoint(ref, CGRectGetMinX(contentRect), CGRectGetMinY(contentRect)+0.5);
  CGContextAddLineToPoint(ref, CGRectGetMaxX(contentRect),   CGRectGetMinY(contentRect)+0.5);
  CGContextStrokePath(ref);
 }

See this blog: http://damir.me/rounded-uiview-with-shadow-the-right-way

Omar Freewan
  • 2,678
  • 4
  • 25
  • 49
  • The link to the git repository in this blog seems to not be working any more. How should I use this on a UICollectionViewCell? – trdavidson Mar 03 '15 at 23:20
  • 1
    Add the above drawrect inside your subclass of UICollectionViewCell @trdavidson – Omar Freewan Mar 04 '15 at 13:04
  • I have been playing around with this code for a while, and encounter the following issues: the subview in the cell are not clipped, such that views in the corners still overlap the rounded corners. Additionally, while scrolling it appears that some cells are 'missed' and do not get the shadow layer at all. Any thoughts on what might cause this? – trdavidson Mar 12 '15 at 06:55
  • make sure you call [yourcell setNeedsToDisplay] in the datasource delegate @trdavidson to see if your problem still presents – Omar Freewan Mar 12 '15 at 12:42
  • which is better for performance? load an UIImage (less than 1kb) from bundle or draw using UIBezierPath ? – Leonardo Cavalcante May 13 '16 at 18:20