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

StackOverflow https://stackoverflow.com/questions/23184460

  •  06-07-2023
  •  | 
  •  

Question

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.

  1. Do I create a UIView for each bar and dynamically resize it's frame height?
  2. Do I draw the bars as thick CGStrokes and redraw them within the parent view as needed?
  3. Another option?

Thanks in advance

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top