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?
I faced a similar problem. I used layers instead of views for the animation. You could try something like this.
- 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];
- 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.
- 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.
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.