I wish to animate a view along an arc as shown in the image from position 1 to position 2.
What actually happens is that the view animation describes a full circle instead of an arc. My questions are:
- Should I be using CGPathAddArc or CGPathAddArcToPoint ?
- Do I need to use CGPathMoveToPoint to describe the initial location of the view or not?
When I step through the code the CGPoints values are as I expect them to be, as are the angles.
I use the following function to get CGPoints on a circle
CGPoint pointOnCircle(CGPoint centre, float radius, float angle) { float x = centre.x + radius * cosf(angle); float y = centre.y + radius * sinf(angle); return CGPointMake(x, y); }
I am using the following code and variants thereof but as yet I have not managed to crack it.
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; pathAnimation.calculationMode = kCAAnimationPaced; pathAnimation.fillMode = kCAFillModeForwards; pathAnimation.removedOnCompletion = NO; pathAnimation.duration = duration; pathAnimation.repeatCount = 1; CGPoint viewCenter = dynamicView.objectCenter; CGMutablePathRef arcPath = CGPathCreateMutable(); // CGPathMoveToPoint(arcPath, NULL, viewCenter.x, viewCenter.y); //work out the half way point float halfwayAngle = dynamicView.angle + (0.5f * self.angleIncrement); float fullwayAngle = dynamicView.angle + self.angleIncrement; CGPoint halfwayPoint = pointOnCircle(self.center, self.radius, halfwayAngle); CGPoint fullwayPoint = pointOnCircle(self.center, self.radius, fullwayAngle); CGPathAddArc(arcPath, NULL, halfwayPoint.x, halfwayPoint.y, self.radius, dynamicView.angle, fullwayAngle, clockwise); // CGPathAddArcToPoint(arcPath, NULL, viewCenter.x, viewCenter.y, fullwayPoint.x, fullwayPoint.y, self.radius); pathAnimation.path = arcPath; CGPathRelease(arcPath); [dynamicView.layer addAnimation:pathAnimation forKey:@"arc"];
I coded up a quick example that hopefully puts it all together. I started from the "single view" application template and added this action to the view controller along with a button to trigger the action.
- (IBAction)animateArc:(id)sender { // Create the arc CGPoint arcStart = CGPointMake(0.2 * self.view.bounds.size.width, 0.5 * self.view.bounds.size.height); CGPoint arcCenter = CGPointMake(0.5 * self.view.bounds.size.width, 0.5 * self.view.bounds.size.height); CGFloat arcRadius = 0.3 * MIN(self.view.bounds.size.width, self.view.bounds.size.height); CGMutablePathRef arcPath = CGPathCreateMutable(); CGPathMoveToPoint(arcPath, NULL, arcStart.x, arcStart.y); CGPathAddArc(arcPath, NULL, arcCenter.x, arcCenter.y, arcRadius, M_PI, 0, NO); // The layer we're going to animate (a 50x50pt red box) UIView* dynamicView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 50, 50)]; dynamicView.layer.backgroundColor = [[UIColor redColor] CGColor]; [self.view addSubview: dynamicView]; dynamicView.center = arcStart; // An additional view that shows the arc. BOOL showArc = NO; UIView* drawArcView = nil; if (showArc) { drawArcView = [[UIView alloc] initWithFrame: self.view.bounds]; CAShapeLayer* showArcLayer = [[CAShapeLayer alloc] init]; showArcLayer.frame = drawArcView.layer.bounds; showArcLayer.path = arcPath; showArcLayer.strokeColor = [[UIColor blackColor] CGColor]; showArcLayer.fillColor = nil; showArcLayer.lineWidth = 3.0; [drawArcView.layer addSublayer: showArcLayer]; [self.view insertSubview: drawArcView belowSubview: dynamicView]; } // The animation CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; pathAnimation.calculationMode = kCAAnimationPaced; pathAnimation.duration = 5.0; pathAnimation.path = arcPath; CGPathRelease(arcPath); // Add the animation and reset the state so we can run again. [CATransaction begin]; [CATransaction setCompletionBlock:^{ [drawArcView removeFromSuperview]; [dynamicView removeFromSuperview]; }]; [dynamicView.layer addAnimation:pathAnimation forKey:@"arc"]; [CATransaction commit]; }
Ultimately I used CGPathAddRelativeArc to get the transition I wanted.
CGPoint viewCenter = dynamicView.objectCenter; CGMutablePathRef arcPath = CGPathCreateMutable(); CGPathMoveToPoint(arcPath, NULL, viewCenter.x, viewCenter.y); float angleOfRotation = self.angleIncrement; if (clockwise == NO) { angleOfRotation *= -1.0f; } float fullwayAngle = dynamicView.angle + angleOfRotation; CGPoint fullwayPoint = pointOnCircle(self.center, self.radius, fullwayAngle); CGPathAddRelativeArc(arcPath, NULL, self.center.x, self.center.y, self.radius, dynamicView.angle, angleOfRotation); pathAnimation.path = arcPath; CGPathRelease(arcPath); [dynamicView.layer addAnimation:pathAnimation forKey:@"arc"]; dynamicView.angle = fullwayAngle; dynamicView.objectCenter = fullwayPoint;