How to Efficiently Draw Shadows on Numerous CALayers in iOS? - ios

I've got some card games which use CALayers to draw individual cards. There can easily be 40 or 50 of them on the screen, which usually works fine.
I recently tried to turn on their shadows using the simple properties for CALayers:
theCardLayer.shadowOffset = CGSizeMake(3,2);
theCardLayer.shadowOpacity = 0.7f;
At that point, the program started getting really laggy. Fair enough; some of the docs said that the shadows could be CPU-intensive.
Any ideas for how to efficiently draw shadows on everything? They're all on the same CALayer in the same UIView, so I'm wondering if there might be a way to pull the mask of the layer or its UIView and shadow that, or something ...
Any functionality up to iOS5 is fair game.

At the very least, try setting your layer's shadowPath property. It can make shadow rendering significantly faster.

Kurt offered up the correct solution. Here's an example of how to use a shadowPath:
UIBezierPath *thisCLPath = [UIBezierPath
bezierPathWithRoundedRect:theCardLayer.bounds
cornerRadius:10.0f];
theCardLayer.shadowPath = thisCLPath.CGPath;
Clearly, I'm using rounded corners here. For a straight-edged layer, you can just use bezierPathWithRect:. There are a few other helpful methods in UIBezierPath as well.
The result is just the right side of laggy on older iOS devices (like an iPhone4 or a mid-generation iPod Touch) and blazing on an iPad3.

Related

What's the best most CPU efficient way to draw views with a lot of animations in iOS?

I'm trying to draw a graphic equaliser for an iOS project.
The equaliser will have 7 bars, representing different frequency bands, than move up and down based on real-time audio data.
Can anyone suggest the best way to approach this in iOS?
New frequency data comes in at about 11Hz, and so the bars would have to animate to a new size 11 times per second.
Do I create a UIView for each bar and dynamically resize it's frame height?
Do I draw the bars as thick CGStrokes and redraw them within the parent view as needed?
Another option?
Thanks in advance
You want to use Core Animation. The basic principle is to create a bunch of "layer" objects, which can either be bitmap images, vector shapes, or text. Each layer is stored on the GPU and most operations can be animated at 60 frames per second.
Think of layers like a DOM node in a HTML page, they can be nested inside each other and you can apply attributes to each one similar to CSS. The list of attributes available matches everything the GPU can do efficiently.
It sounds like you want vector shapes. Basically you create all your shapes at startup, for example in the awakeFromNib method of a UIView subclass. For simple rectangles use CALayer and set a background colour. For more complicated shapes create a CAShapeLayer and a UIBezierPath, then apply it with shapeLayer.path = bezierPath.CGPath;.
Then, whenever you want to change something, you apply those changes to the layer object. For example, here I'm rotating a layer with a 1 second linear animation:
[CATransaction begin];
[CATransaction setAnimationDuration:1];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[self.needleLayer setValue:[NSNumber numberWithFloat:DegreesToRadians(degrees) forKeyPath:#"transform.rotation.z"];
[CATransaction commit];
// you'll want to declare this somewhere
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
}
More complicated animations, eg a series of changes scheduled to execute back to back, can be done using a CAKeyframeAnimation: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html
Note Core Animation only does 2D graphics. Apple has Scene Kit which is basically the same thing for 3D, but so far it's only available on OS X. Hopefully iOS 8 will include it, but until then if you want 3D graphics on iOS you need to use Open GL.
CALayers which you resize on demand would probably be the most efficient way to do this, if the bars are solid colours. This allows you to optionally animate between sizes as well.
View resizing triggers off layout cycles, which you don't want. Drawing using CG calls is pretty slow.
Obviously the only real way to find out is to profile (on a device) using instruments and the core animation tool. But from experience, layer sizing is faster than drawing.
Definitely not a UIView for each - instead, a single UIView for the entire equalizer. Fill in the drawRect method with the appropriate CG calls to draw whatever is required. You can queue the view to refresh as needed with the appropriate data. Tapping into CADisplayLink will help you get the frame-rate you're looking for.
https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
NOTE: You can also subclass CALayer and draw in it if you prefer something lighter-weight than UIView but I think you'll be fine with the former.

CALayer Antialiasing not as good as UIView antialiasing

I've been trying to animate circle drawing using CALayer. It all works well, but the problem is - drawn circle is not antialiased enough. It has a bit too rough borders, (or blurred if rasterize is used). (AntiAliasing is enabled)
Tried also:
edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge |
kCALayerBottomEdge | kCALayerTopEdge;
to no avail.
Here is an example how it looks like without rasterization:
And here is an example with rasterization: (tried values from 1.0 till 4.0 (just to be sure. Result - the same.))
And here is the same circle, but drawn inside UIView drawrect:
You can see, that circle drawn using UIView drawrect is looking much better.
The reason I cannot use UIView is because I need to animate circle filling. Using CALayer it is really easy, but to do the same on UIView, I don't really know if it is even possible. (I could try to launch drawrect: every 1/60 seconds, but I think it will get laggy, as it is not intended that way).
So - does anyone have any solution how I could make drawn circles/lines on CALayer look the same as drawn on UIView?
I've had issues with pixelated drawing in a CALayer on retina devices before. (I'm assuming you're seeing the issue on retina devices) Doing the following fixed the issue I was experiencing:
layer.contentsScale = [[UIScreen mainScreen] scale];
You shouldn't need to deal with rasterization or antialiasing. In my own code, I had initially implemented drawing something to a UIView that I later changed to be drawn in a CALayer, and simply setting the contentsScale property made both draw identically.

drawing shadow using core graphic and using CALayer

As far as I know we can use core graphic such as CGContextSetShadowWithColor to draw a shadow. However, we can also use CALayer to show the shadow as well.
Question :
what are the differences between 2 of them. Are there any rules to determine when we use core graphic to draw a or when we use CALayer to do the job
I would have to say that using CoreAnimation is always preferred over CoreGraphics, since it's more high level, and abstracts the low-level details of drawing the shadow. (It may also allow apple to optimize the shadow drawing without hurting your code syntax).
However, there are times where you are overriding drawRect: anyways, and you have very specific use for the shadow, not the whole view's layer. You might wanna use CoreGraphics shadows here.
One last note, CoreAnimation gradients are much faster when rendering, take my word for it. I used it on UITableViewCell, and the scroll performance significantly increased, as opposed to using CoreGraphics Gradients. That comes at a price, though. It's a bit worse-looking.

iOS CAShapeLayer and shadows with Core Animation

I would like to draw a graph with CAShapeLayer and set a pretty shadow on a background for some nice visual effect. With shadows, animation of my graph (straight line to graph modeled using some data) really slows down!. I read about shadowPath property and decided to use it, but the problem is, despite the fact wether I close the CGMutable path or not, it seems like CoreAnimation closes it, and this shadow I get, instead of a line, is rather a polygon. Looks really ugly.
Help!

iOS Screen Rotation Degraded When Shadows Are Visible

This isn't in reference to any particular code, but I've noticed that when I have a UIView that has a shadow added to it's layer, the animation when rotating between interface orientations becomes much more laggy/choppy.
Has anyone noticed this issue or found a workaround?
When using shadows, the shadowPath property of CALayer makes a very (!) noticable difference in performance, especially on the New iPad. Although I agree that disabling shadows when changing the display orientation is a good idea, it may be worth a try to just use shadowPath (if you do not use it already). Although the path can be any valid CGPathRef, in most cases this is what you want:
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
There are things which are very expensive in terms of CPU time. Check it out in Instruments some time.
shadows
bezier paths
bezier paths with dashes (really expensive)
Thats not a comprehensive list. I suspect gradients will be there too.
If you find these things are degrading your animation or redraw you will need to toggle them in the UIViewController methods.
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//disable shadows + expensive drawing
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
//enable shadows + expensive drawing
}

Resources