Samarth D R Samarth D R - 1 month ago 19
iOS Question

Is it possible to update the position of a button after a CAKeyFrameAnimation?

I have been trying to update the position of button(s) after the completion of a

. As of now, I have stopped the animation at a certain angle on the arc (final angle) but, it seems the touches the buttons take is at the point it was earlier in before the animation takes place.

Here's the code that I have been using:

for(int i = 0; i <numberOfButtons; i++)
double angle = [[self.buttonsArray objectAtIndex:i] angle];

if(angle != -50 && angle !=270){
CGPoint arcStart = CGPointMake([[self.buttonsArray objectAtIndex:i] buttonForCricle].center.x, [[self.buttonsArray objectAtIndex:i] buttonForCricle].center.y);
CGPoint arcCenter = CGPointMake(centerPoint.x, centerPoint.y);
CGMutablePathRef arcPath = CGPathCreateMutable();
CGPathMoveToPoint(arcPath, NULL, arcStart.x, arcStart.y);
CGPathAddArc(arcPath, NULL, arcCenter.x, arcCenter.y, 150, ([[self.buttonsArray objectAtIndex:i] angle]*M_PI/180), (-50*M_PI/180), YES);
UIButton *moveButton = (UIButton *)[self.view viewWithTag:i+1];
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
// pathAnimation.values = @[@0];
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 1.0;
pathAnimation.path = arcPath;

// UIView* drawArcView = nil;
// 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 = 1.0;
// [drawArcView.layer addSublayer: showArcLayer];
// [self.view addSubview:drawArcView];

[moveButton.layer addAnimation:pathAnimation forKey:@"arc"];
[CATransaction commit];
[self.view bringSubviewToFront:moveButton];



How can I update the buttons position after this?


Why this is happening

What you're seeing is that animations only change the "presentation values" of a layer, without changing the "model values". Add to this the fact that animations by default are removed when they complete and you have an explanation for why the button(s) would return to their original position after the animation finishes.

This is the state of the actual button, and the location where it is hit tested when the user taps on it.

The combination of removedOnCompletion = NO and fillMode = kCAFillModeForwards makes it look like the button has moved to it's final position, but it's actually causing the difference between presentation values and model values to persists.

Put simply, the button looks like it's in one position, but it's actually in another position.

How to fix it

For this type of situation, I recommend to let the animation be removed when it finishes, and to not specify any special fill mode, and then solve the problem of the button(s) position being incorrect.

They way to do this is to set the buttons position/center to its final value. This will make it so that the button's model position changes immediately, but during the animation its presentation position remains the same. After the animation finishes and is removed the button goes back to its model value, which is the same as the final value of the animation. So the button will both look and be in the correct position.

Usually the easiest place to update the model value is after having created the animation object but before adding it to the layer.

Changing the positions will create and add an implicit animation. But as long as the explicit animation (the one you're creating) is longer, the implicit animation won't be visible.

If you don't want this implicit animation to be created, then you can create a CATransansaction around only the line where the position property is updated, and disable actions (see setDisableActions:).

If you want to learn more, I have a blog post about how layers work with multiple animations and an answer to a question about when to update the layer.