Animation along path - Completion method called too early - ios

I'd like to implement an animation along a rounded path and fire a completion function as soon as the animation has ended.
Thanks to this post I figured out how to use a CAKeyframeAnimation with a CGMutablePathRef to perform the animation along a path.
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 1.0;
CGPoint endPoint = CGPointMake(self.boardReference.view.bounds.size.width/2,self.boardReference.view.bounds.size.height/2);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, self.view.center.x, self.view.center.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, self.view.center.y, endPoint.x, self.view.center.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
[self.view.layer addAnimation:pathAnimation forKey:#"curvedPath"];
[self addNewCardAtPoint:endPoint]; //This should only be launched after the animation has been finished
Problem: My method addNewCardAtPoint should only be called after the animation has been completed. Currently the process is not blocking so the method is called more or less simultaneously.
At the first view the animateWithDuration function seems to better suited as it has defined blocks for the animation and the completion action. But simply adding the CAKeyframeAnimation to the animations block has basically the same result, immediate execution of the completion block.
[UIView animateWithDuration:0.8
animations:^{
NSLog(#"-Animate on curved path to end point-");
... //how perform the same path based animation here?
} completion:^(BOOL finished) {
[self addNewCardAtPoint:self.view.center];
}
];
How can I fire a complete function only after the animation along the curved path has been completed?

When a CAAnimation is done, it will call the animationDidStop:finished: method on its delegate.

Related

Animation subview removal freezes when scrolling

My code:
I am using the following code for an add to basket animation:
-(void)triggerAddItemAnimation:(NSIndexPath*)indexPath
{
//Cell that was pressed.
CellMenuItem *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// grab the imageview using cell
UIImageView *imgV = (UIImageView*)[cell viewWithTag:IDENTIFIER_ANIMATION_IMAGE];
//Get the x-coordinate of the image.
int xCoord = cell.imgAddButton.frame.origin.x;
// get the exact location of image
CGRect rect = [imgV.superview convertRect:imgV.frame fromView:nil];
rect = CGRectMake(xCoord, (rect.origin.y*-1)-10, imgV.frame.size.width, imgV.frame.size.height);
//NSLog(#"rect is %f,%f,%f,%f",rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);
// create new duplicate image
UIImageView *starView = [[UIImageView alloc] initWithImage:imgV.image];
[starView setFrame:rect];
starView.layer.cornerRadius=5;
starView.layer.borderColor=[[UIColor blackColor]CGColor];
starView.layer.borderWidth=1;
[self.view addSubview:starView];
// begin ---- apply position animation
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration=0.65;
pathAnimation.delegate=self;
// tab-bar right side item frame-point = end point
CGPoint endPoint = CGPointMake(self.lblBasketItemsCount.frame.origin.x, self.lblBasketItemsCount.frame.origin.y);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, starView.frame.origin.x, starView.frame.origin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, starView.frame.origin.y, endPoint.x, starView.frame.origin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
// end ---- apply position animation
float animationDuration = 0.65f;
// apply transform animation
CABasicAnimation *basic=[CABasicAnimation animationWithKeyPath:#"transform"];
[basic setToValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.25, 0.25, 0.25)]];
[basic setAutoreverses:NO];
[basic setDuration:animationDuration];
[starView.layer addAnimation:pathAnimation forKey:#"curveAnimation"];
[starView.layer addAnimation:basic forKey:#"transform"];
//Remove the animation 0.05 before the animationDuration to remove clipping issue.
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:animationDuration - 0.05f];
}
The Problem:
The animation works beautifully but when I scroll the image used for the animation (taken from the cell) does not remove from the view until the scrolling has stopped.
The subview being removed is removed with the last line of code within the method:
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:animationDuration - 0.05f];
What I've tried:
Ive tried using GCD:
dispatch_async(dispatch_get_main_queue(), ^{
//call method here....
});
If I recall I believe Ive also tried a run loop with a timer, as well as perform selector on main thread.
Ive also tried putting the method in a block(not the actual code I used, merely an example):
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
}
completion:^(BOOL finished){
//Remove subview here
}];
The Question:
I can deduce that the scrolling is tying up the main thread, hence why I've tried to call the method asynchronously.
Can someone let me know what I'm doing wrong ?
To solve this issue you can use the complete portion of a block as can be seen above. I was simply using the block incorrectly at the time.
However the way I solved this, because I couldn't get a path animation to work in a block was to set the delegate of the animation to self.
And use the following:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[theView removeFromSuperview];
}

How can I trigger my animation after a specific segue?

I have an animation that is currently working. I followed this answer Xcode How to move an image up and down animation
I was able to get the animation triggered with a button. What I really want is to trigger the animation after a specific segue. Is it possible to trigger this animation when I segue to a specific view in my storyboard?
The animation is in a method inside my ViewController implementation.
-(void)animatePicker
{
NSLog(#"Animate");
CGPoint startPoint = (CGPoint){100.f, 100.f};
CGPoint middlePoint = (CGPoint){400.f, 400.f};
CGPoint endPoint = (CGPoint){600.f, 100.f};
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(thePath, NULL, middlePoint.x, middlePoint.y);
CGPathAddLineToPoint(thePath, NULL, endPoint.x, endPoint.y);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = 3.f;
animation.path = thePath;
[pickerCircle.layer addAnimation:animation forKey:#"position"];
pickerCircle.layer.position = endPoint;
}
Implement a -(void)viewDidAppear; method in your UIViewController and call your animation function there.
This function is called when UIViewController view has been created and has appeared as well.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"viewDidAppear");
[masterViewController animate];
}

IOS current keyframe y axis

Hey im trying to show the y axis of my imageview while its moving , but its not displaying the animating y axis , its showing only one number .
//LINE ANAMATION
CALayer *layer = line.layer;
CGPoint startPoint = (CGPoint){line.center.x,20};
CGPoint endPoint = (CGPoint){line.center.x, screenSizeY/2};
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(thePath, NULL, endPoint.x, endPoint.y);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = 3.f;
animation.path = thePath;
animation.autoreverses = YES;
animation.repeatCount = INFINITY;
[layer addAnimation:animation forKey:#"position"];
NSLog(#"%f", line.center.y);
There are a couple of things wrong with this. First, the animation state is maintained in the presentation layer. Your view and underlying layer are will reflect the final state of the animation immediately. Second, your NSLog statement will only be called once because you aren't in a loop.
If you want to do something at each frame of an animation, you can use CADisplayLink to get a callback on each frame. In this callback, you can check the current Y position by looking at the presentation layer:
- (void)myCallback:(CADisplayLink *)link
{
NSLog(#"%f", CGRectGetMidY(line.layer.presentationLayer.frame));
}

CAKeyframeAnimation repeat animation from begin to end

I need to add infinite animation for UIImageView from start point to end point and after that from end point to start point.
I am trying like this:
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 2.0;
pathAnimation.repeatCount = HUGE_VALF;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CGPoint endPoint = CGPointMake(320.0f - 30.0f, self.myImage.frame.size.height);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, self.myImage.frame.origin.x, self.myImage.frame.origin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, self.myImage.frame.origin.y,
endPoint.x, self.myImage.frame.origin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
[self.myImage.layer addAnimation:pathAnimation forKey:#"curveAnimation"];
myImage moves from start point to end ok, but after that move to start point and repeat animation from start point to end. So, There is no animation from end point to start. Is it possible to do?
Animating back the same way as you came is as simple as setting autoreverses to YES.
That said, If it's truly an infinite animation then you don't need the fillMode or the removedOnCompletion
I were you, I'd simply use :
[UIVIew animateWithDuration:delay:options:animations:completion:]
with the options
UIViewAnimationOptionRepeat

CALayer setPosition not called during animation

I have a custom CALayer that I am animating using a CAAnimationGroup to follow a path and rotate at a tangent to the path:
// Create the animation path
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
//Setting Endpoint of the animation
CGRect contentBounds = [self contentBounds];
self.boatLayer.bounds = contentBounds;
CGPoint endPoint = CGPointMake(contentBounds.size.width - 150, contentBounds.size.height - 150);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, startPosition.x, startPosition.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, 0, endPoint.x, 0, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
pathAnimation.duration = 10.0;
pathAnimation.rotationMode = kCAAnimationRotateAuto;
pathAnimation.delegate = self;
// Create an animation group of all the animations
CAAnimationGroup *animationGroup = [[[CAAnimationGroup alloc] init] autorelease];
animationGroup.animations = [NSArray arrayWithObjects:pathAnimation, nil];
animationGroup.duration = 10.0;
animationGroup.removedOnCompletion = NO;
// Add the animations group to the layer (this starts the animation at the next refresh cycle)
[testLayer addAnimation:animationGroup forKey:#"animation"];
I need to be able to track the changes to the position and rotation of the layer as it progresses along the path. I have overridden both setPosition and setTransform (calling super setPosition and super setTranform) and then logging their values. Neither of these values appear to be set during the animation.
How can I get the position and rotation updates from within the CALayer class itself as it animates?
Core Animation doesn't work like that
Sorry. That is not how Core Animation work. When you add an animation to a layer it doesn't change the model of that layer. Only the presentation.
When you configure then animation to not remove itself upon completion
yourAnimation.fillMode = kCAFillModeForwards;
tourAnimation.removedOnCompletion = NO;
you are actually causing an inconsistency between what is shown on screen and the model of that layer. If you for instance had a button that you animated like this you would get very surprised/angry by the fact that it "no longer responds to toucher" or even more funny "responds to touches from it's 'old' location".
Semi-solutions
Depending on what and how often you actually need the updates you could either periodically check the value of the presentationLayer during the animation or use a CADisplayLink to run some code when the screen changes.

Resources