I am doing a very simple repeating animation to fade a label in and out, seen below. I assumed the completion block would be called every time the animation finishes, but when using UIViewAnimationOptionRepeat it is never called. So how am I supposed to stop this animation?
I know I can use [self.lbl.layer removeAllAnimations];, however that ends its very abruptly. I want to know when it has finished a cycle of the animation so I can stop it at that time.
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut animations:^{
self.lbl.alpha = 0;
} completion:^(BOOL finished){
if (finished) NSLog(#"done");
}];
If you want the animation with the UIViewAnimationOptionRepeat option to be finite, you have to set the repeat count via + (void)setAnimationRepeatCount:(float)repeatCount of UIView.
In case of block-based animation (that is your case), you should set repeat count from within animation block. So, here is your code modified:
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut animations:^{
[UIView setAnimationRepeatCount:4]; // 4 as example here
self.lbl.alpha = 0;
} completion:^(BOOL finished){
if (finished) NSLog(#"done");
}];
This is mentioned in documentation of setAnimationRepeatCount: method of UIView.
maybe this kind of solution using a selector can help you:
- (void) animateTextWithMax:(NSNumber *)max current:(NSNumber *)current
{
NSLog(#"max = %d, current = %d",max.intValue, current.intValue);
textlabel.alpha = 1.0f;
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionAutoreverse
animations:^{
textlabel.alpha = 0.0f;
}completion:^(BOOL finished){
NSLog(#"finished");
if (current.intValue < max.intValue) {
[self performSelector:#selector(animateTextWithMax:current:) withObject:max withObject:[NSNumber numberWithInteger:(current.intValue+1)]];
}
}];
}
then you can call the animation by this way:
[self animateTextWithMax:[NSNumber numberWithInt:3] current:[NSNumber numberWithInt:0]];
Maybe this is not the best solution because you are not using the UIViewAnimationOptionRepeat option, but I think it can work.
I hope this helps you.
As you've discovered, the completion block is not called, because the animation never ends. Off the top of my head, you've got two options:
Add another animation to the label, setting it to your desired final value, with the UIViewAnimationOptionBeginFromCurrentState option. I'm not sure what effect this has on the existing, repeating animation, though. If that doesn't work...
Get the current position of your animation by examining the label.layer.presentationLayer. Stop all animations, set your value to the current state, then add a new animation to transition from the current state to the desired final state.
Related
I have a animation view. When the animation is finished I would like to show a new view by setting its hidden to false.
I set the hidden to false after the animation block code, but it seems like the animation is being done on a separate thread. The view gets unhidden while the animation block is still playing
// start animation block
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut ];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:mCards[r] cache:YES];
// mCards[0].image = [UIImage imageNamed:#"card.png"]; //begin
int id=TarretWheel[r];
mCards[r].image = gCardImages[id]; //end
// Start animtion
[UIView commitAnimations];
// show view
mBackground.hidden=false;
You can (and preferably should, actually), be using the newer block-based animation methods on UIView. Check out the following (it has a completion block which is just what you need):
[UIView animateWithDuration:0.5 animations:^{
} completion:^(BOOL finished) {
}];
Edit:
There are other variants with options that you may want in your case.
Also, clarification on why you 'should' be using the block-based methods; from the docs (for beginAnimations:context:):
Use of this method is discouraged in iOS 4.0 and later. You should use
the block-based animation methods to specify your animations instead.
My answer is same as Joe but I would recommend using a different API which allows you with configure block.
[UIView animateWithDuration:2
delay:0.0
options:UIViewAnimationOptionTransitionFlipFromLeft|UIViewAnimationOptionCurveEaseOut |
animations:^{
// mCards[0].image = [UIImage imageNamed:#"card.png"]; //begin
int id=TarretWheel[r];
mCards[r].image = gCardImages[id]; //end
}
completion:^(BOOL finished){
mBackground.hidden=false;
}
];
I use animationWithDuration for animating elements. An I have 1 animation inserted in completion block of another.
[UIView animateWithDuration:2.0 delay:0.1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
} completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 delay:0.1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
} completion:^(BOOL finished) {
... //and simple
}];
}];
So I have Stairs of six - nine elements of blocks of animationWithDuration.So how I can optimize this?
Sorry for my English.
If you have many nested animations the code quickly becomes very unreadable.
So the approach here is maybe to hold an array of all animation blocks and to iterate through them without nesting them.
One possible example how this looks in code is shown here:
Multiple UIView Animations Without Nested Blocks
I'm doing two animations on the same UIImageView, using blocks. Animations are not quite back to back, but almost; there is some logic inbetween.
animate UIImageView from one location on the view to another.
execute logic that determines whether image is allowed to stay there
if not allowed, undo the animation (UIImageView springs back to original location)
If I implement this as above, only the second animation shows (this is normal behavior from what I understand). If I nest the logic and the second animation block inside the completion block of the first, I see both animations, but there's a fair amount of code to jam into that completion block and it just seems ugly and out of place.
In the non-nested configuration, why does iOS want to cut short the previous animations and execute only the final one, and how can I force it to wait on the first one before going to the next? I don't think it needs to block the main thread or "sit and spin" in a completion block; I just want all animations to be shown. Tried adding delay to second animation to no avail.
Is this a job for CAKeyframeAnimation?
// first animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = newCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"forward animation done");}];
if (/* ...color is allowed at that location */) {
// do some stuff
} else {
// undo the animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
}
The second animation should be done conditionally inside the first's completion block. Putting it into a function will make it more readable.
- (void)moveColor:(UIView *)view to:(CGPoint)center delay:(NSTimeInterval)delay completion:(void (^)(BOOL finished))complete {
[UIView animateWithDuration:0.5 delay:delay options:UIViewAnimationCurveLinear animations:^{
view.center = center;
view.transform = scaleTransform; // probably don't need this
} completion:completion];
}
This way your logic can be separate from the animation code
- (void)someMethod {
[self moveColor:movingColor to:newCenter delay:0.0 completion:^(BOOL finished) {
if (/*it can stay*/) {
// stuff
} else {
[self moveColor:movingColor to:origCenter delay:2.0 completion:^(BOOL finished) {}];
}
}];
}
The correct way to do it is as you said to set the second one in the completition of the first one that is the correct way,
You can also adda delay on the start of the other, this may or may not work it depends on alot of variables
Your second animation will have a delay of .5 (time for first animation to complete )
[UIView animateWithDuration:0.5
delay:0.5
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
The animateWithDuration is executed asynchronously, and returns right away. That is, the next line of code is executed without waiting for the animation to finish. You either put your code in the completion block if you want it to be executed after the animation is finished, or you accept that the second animation is started (thus canceling the first) immediately.
If you want to indicate to the user that something was wrong by starting the animation, but not completing it, you could execute the second animation with a delay. Remember to also set the the UIViewAnimationOptionBeginFromCurrentState option if you delay the second animation with less than the duration of the first:
[UIView animateWithDuration:0.5
delay:0.25 //or some number
options: (UIViewAnimationCurveLinear | UIViewAnimationOptionBeginFromCurrentState)
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
I've made something like:
[UIView animateWithDuration:2.0
delay:0
options:UIViewAnimationOptionRepeat
animations:^{
NSLog(#"Repeating...");
/** code **/
}
completion:^(BOOL finished) {
/** code **/
}];
But the animation does not repeat itself.
Am I missing something?
I've removed the color animation, but the animation doesn't repeat yet. But I must be doing something really wrong, 'cause I tried the following simple code and the animation still doesn't repeat:
[UIView animateWithDuration:1.0f
delay:0
options: UIViewAnimationOptionRepeat
animations:^{
NSLog(#"Repeating...");
self.alpha = 1;
}
completion:nil];
Depending on the animation (still would like to see your code) you might have to reverse your animation in order for it to repeat. For example, if your animation changes an alpha value from 0 to 1, repeating that, without reversing the alpha back to 0, isn't going to do much. You may need to add UIViewAnimationOptionAutoreverse to your animation options.
I am moving a UIImageView with the following animation:
[UIView animateWithDuration:.5 delay:0 options:(UIViewAnimationCurveLinear | UIViewAnimationOptionAllowUserInteraction)
animations:^{
myImage.frame = CGRectOffset(myImage.frame, myImageOffset.x, myImageOffset.y);
}
completion:^(BOOL finished){
}
];
The problem is the image starts moving slow, then speeds up and slows down again before ending the animation. Isn't the UIViewAnimationCurveLinear option supposed to make the animation perform at a constant rate?
Try using this option instead - UIViewAnimationOptionCurveLinear. I found it while looking at this question on another site - http://www.iphonedevsdk.com/forum/iphone-sdk-development/89009-block-animation-ignores-uiviewanimationcurvelinear.html