I am trying to move a plane along a path as the user is defining it, similar to the controls in Flight Control. I am able to draw the path, albeit inefficiently, but I cannot get the plane to animate along the path smoothly.
I originally tried to move the plane along the path by using the implicit animations provided by changing the position, but because it does not allow me to alter the speed of the animation, the plane behaves badly.
I am now trying to use an animation block recursively, but the completion block is being called way too soon, resulting in the plane just following the path being drawn.
This is the setup code:
- (CALayer *)plane{
if(!_plane){
_plane = [CALayer layer];
UIImage *planeImage = [UIImage imageNamed:@"airplane.png"];
_plane.bounds = CGRectMake(20.0, 20.0, planeImage.size.width, planeImage.size.height);
_plane.contents = (id)(planeImage.CGImage);
[self.layer addSublayer:_plane];
}
return _plane;
}
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event{
if(isDrawingPath){
[self.trackPath addLineToPoint:[[touches anyObject] locationInView:self]];
NSLog(@"%@", self.trackPath);
UITouch *touch = [touches anyObject];
CGPoint toPoint = [touch locationInView:self];
//now, save each point in order to make the path
[self.points addObject:[NSValue valueWithCGPoint:toPoint]];
[self setNeedsDisplay];
[self goToPointWithIndex:0];
}
isDrawingPath = NO;
}
This implementation works, but badly. The plane follows the path, but it is choppy:
- (void)goToPointWithIndex:(NSNumber *)indexer{
int toIndex = [indexer intValue];
if(toIndex < self.points.count){
//extract the value from array
CGPoint toPoint = [(NSValue *)[self.points objectAtIndex:toIndex] CGPointValue];
CGPoint pos = self.plane.position;
float delay = PLANE_SPEED * (sqrt( pow(toPoint.x - pos.x, 2) + pow(toPoint.y - pos.y, 2)));
self.plane.position = toPoint;
// Allows animation to continue running
if(toIndex < self.points.count - 1){
toIndex++;
}
//float delay = 0.2;
NSLog(@"%f", delay);
//repeat the method with a new index
//this method will stop repeating as soon as this "if" gets FALSE
NSLog(@"index: %d, x: %f, y: %f", toIndex, toPoint.x, toPoint.y);
[self performSelector:@selector(goToPointWithIndex:) withObject:[NSNumber numberWithInt:toIndex] afterDelay:delay];
}
}
This is what I am trying to do with blocks. It just skips to the end of the path drawn instead of follow the entire thing.
- (void)goToPointWithIndex:(int)toIndex{
if(self.resetPath) return;
//extract the value from array
if(toIndex < self.points.count) {
CGPoint toPoint = [(NSValue *)[self.points objectAtIndex:toIndex] CGPointValue];
NSLog(@"index: %d, x: %f, y: %f", toIndex, toPoint.x, toPoint.y);
CGPoint pos = self.plane.position;
//float delay = PLANE_SPEED * (sqrt( pow(toPoint.x - pos.x, 2) + pow(toPoint.y - pos.y, 2)));
// Allows animation to continue running
if(toIndex < self.points.count - 1){
toIndex++;
}
[UIView animateWithDuration:0.2
animations:^{
self.plane.position = toPoint;
}
completion:^(BOOL finished) {
if(finished == YES) {NSLog(@"Complete");[self goToPointWithIndex:toIndex];}
}];
}
}
I have no idea where I'm going wrong. I'm new to objective-c and blocks. Is the completion block supposed to run right after the animation begins? It doesn't make sense to me, but its the only explanation I can find.
EDIT: I ended up making the plane a UIView so I could still use block animations. The CALayer transactions did not like my recursion very much at all.
Since here self.plane
is a custom CALayer
(as opposed to the layer backing a UIView
), it will not respect the parameters of UIView animateWithDuration:
From the docs:
Custom layer objects ignore view-based animation block parameters and use the default Core Animation parameters instead.
Instead use a CABasicAnimation
and CALayer's
addAnimation:forKey:
method (and probably in this case a CAAnimationGroup
). How to animate CALayers
like this is described in the docs under "Animating Layer Content."