12

I have two circles which move around the screen. The circles are both UIViews which contain other UIViews. The area outside each circle is transparent.

I have written a function to create a CGPath which connects the two circles with a quadrilateral shape. I fill this path in a transparent CALayer which spans the entire screen. Since the layer is behind the two circular UIViews, it appears to connect them.

Finally, the two UIViews are animated using Core Animation. The position and size of both circles change during this animation.

So far the only method that I have had any success with is to interrupt the animation at regular intervals using an NSTimer, then recompute and draw the beam based on the location of the circle's presentationLayer. However, the quadrilateral lags behind the circles when the animation speeds up.

Is there a better way to accomplish this using Core Animation? Or should I avoid Core Animation and implement my own animation using an NSTimer?

titaniumdecoy
  • 18,900
  • 17
  • 96
  • 133

2 Answers2

13

I faced a similar problem. I used layers instead of views for the animation. You could try something like this.

  1. Draw each element as a CALayer and include them as sublayers for your container UIVIew's layer. UIViews are easier to animate, but you will have less control. Notice that for any view you can get it's layer with [view layer];
  2. Create a custom sublayer for your quadrilateral. This layer should have a property or several of properties you want to animate for this layer. Let's call this property "customprop". Because it is a custom layer, you want to redraw on each frame of the animation. For the properties you plan to animate, your custom layer class should return YES needsDisplayForKey:. That way you ensure -(void)drawInContext:(CGContextRef)theContext gets called on every frame.
  3. Put all animations (both circles and the quad) in the same transaction;

For the circles you can probably use CALayers and set the content, if it is an image, the standard way:

layer.contents = [UIImage imageNamed:@"circle_image.png"].CGImage;

Now, for the quad layer, subclass CALayer and implement this way:

- (void)drawInContext:(CGContextRef)theContext{
  //Custom draw code here
}   
+ (BOOL)needsDisplayForKey:(NSString *)key{
   if ([key isEqualToString:@"customprop"])
      return YES;
    return [super needsDisplayForKey:key];
}   

The transaction would look like:

[CATransaction begin];
CABasicAnimation *theAnimation=[CABasicAnimation animationWithKeyPath:@"customprop"];

theAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1000, 1000)];
theAnimation.duration=1.0;
theAnimation.repeatCount=4;
theAnimation.autoreverses=YES;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.delegate = self;
[lay addAnimation:theAnimation forKey:@"selecting"];

[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
                     forKey:kCATransactionAnimationDuration];
circ1.position=CGPointMake(1000, 1000);
circ2.position=CGPointMake(1000, 1000);
[CATransaction commit];

Now all the draw routines will happen at the same time. Make sure your drawInContext: implementation is fast. Otherwise the animation will lag.

After adding each sublayer to the UIViews's layer, rememeber to call [layer setNeedsDisplay]. It does not get called automatically.

I know this is a bit complicated. However, the resulting animations are better than using a NSTimer and redrawing on each call.

fsaint
  • 8,759
  • 3
  • 36
  • 48
  • Just a note for those who choose to implement fully programmatic animation instead of using custom properties: don't use `NSTimer` for that; it would be slow and CPU-greedy. For anything animation-related, use [`CADisplayLink`](https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/), which is synchronized with screen refresh rate and is used internally by CoreAnimation. – skozin Apr 09 '16 at 00:20
1

If you need to find the current visible state of the layers, you can call -presentationLayer on the CALayer in question, and this will give you a layer that approximates the one used for rendering. Note I said approximates - it's not guaranteed to be fully accurate. However it may be good enough for your purposes.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • As I mentioned, I have already tried using the presentationLayer but it lags behind the circles. I need a more reliable method of synchronizing the animation of multiple components. – titaniumdecoy Nov 29 '10 at 22:23
  • The only reliable way I can think of is to tie into the animation machinery. You could look into using a CAShapeLayer and animating its path in sync with the circles. – Lily Ballard Nov 30 '10 at 02:53