Animation fails to resume through switching tabs - iphone

I've seen a bunch of similar questions on here, but none of them had what I was looking for.
Anyways:
I'm animating an Annotation on a map to move up and down. I do this by calling this function in its drawRect:
- (void) wiggle {
[UIView animateWithDuration:0.45 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
[self.subView setFrame:CGRectMake(self.subView.frame.origin.x, self.subView.frame.origin.y+WIGGLE_DISTANCE, self.subView.frame.size.width, self.subView.frame.size.height)];
} completion:^(BOOL finished) {
if (finished)
[UIView animateWithDuration:0.45 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
[self.subView setFrame:CGRectMake(self.subView.frame.origin.x, self.subView.frame.origin.y-WIGGLE_DISTANCE, self.subView.frame.size.width, self.subView.frame.size.height)];
} completion:^(BOOL finished) {if (finished) [self wiggle]; }];
}];
}
In short, it's animating a 'subView' up, and when that finishes, animating down, and when that finishes, calls itself (to continue wiggling).
This works fine until I switch tabs. When I return to this tab, there is no animation. I've tried calling 'setNeedsDisplay' on each of these views in the viewController's 'viewDidAppear', and that fails to call drawRect. So then I tried just calling 'wiggle' directly from the viewController's 'viewDidAppear', and the function DOES get called, but it just doesn't animate. Any ideas?
Thanks!

Related

Interrupt completion block based animations to perform separate animations

I am using block based animations to simulate dealing cards as an intro animation for a game. The animation works great unless the user causes a segue to fire DURING the animation, where we perform additional animations in order to get the effect a "sliding" transition from source to destination view controller. What happens now is the cards that have already been "dealt" slide off screen appropriately, and if there are cards that have not been dealt, it deals it in the middle of the transition and then the card disappears when the destination view controller is pushed. It's very ugly.
I have tried 'view.layer removeAllAnimations' which didn't help (I did import quartzcore). What I want to do is cancel the pending animations in the completion blocks, and simply perform the segue animations.
Here's the "dealing" code:
[UIView animateWithDuration:0.20f
delay:0.20f
options:UIViewAnimationOptionCurveEaseOut
animations:^
{
_fiveOfHearts.center = CGPointMake(90, 198);
_fiveOfHearts.transform = fiveOfHeartsTransform;
}
completion:^(BOOL finished)
{[UIView transitionWithView:_fiveOfHearts duration:0.20f options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
_fiveOfHearts.image = [UIImage imageNamed:#"52"];
}completion:nil];
[UIView animateWithDuration:0.30f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^
{
_jackOfHearts.center = CGPointMake(128, 196);
_jackOfHearts.transform = jackfHeartsTransform;
}
completion:^(BOOL finished)
{[UIView transitionWithView:_jackOfHearts duration:0.40f options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
_jackOfHearts.image = [UIImage imageNamed:#"112"];
}completion:nil];
[UIView animateWithDuration:0.30f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut
animations:^
{
_aceOfHearts.center = CGPointMake(162, 196);
_aceOfHearts.transform = aceOfHeartsTransform;
}
completion: ... and so on.
The segue code looks something like:
for (UIView *iv in src.view.subviews) {
if (iv.tag != 99999) {
[UIView animateWithDuration:0.5f animations:^{iv.center = CGPointMake(iv.center.x - 600, iv.center.y);}];
}
}
You could add a global BOOL say hasUserSkippedOn once the user presses to move on set it to YES and then every time you hit a completion block check if _hasUserSkipped is still YES then do not proform any more. Normally on blocks it has a default end bool but I am not too sure if animations blocks have the end bool.

UIView animateWithDuration returns immediately

I'm trying to animate a label embedded in a UIView.
this is the code :
-(void)displayText:(NSString*)text {
[label setText:text];
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[labelView setAlpha:1.0];
}
completion:nil
];
[UIView animateWithDuration:5.8
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[labelView setAlpha:0.0];
}
completion:nil
];
}
To verify, the method is called, i set a breakpoint.
The calls return immediatly but only the end of animations is displayed.
I wired the UIView to the controller.
Pls help, I'm stuck.
Thanks in advance !
Patrick
Correct,
When you animate views like this the animation doesn't actually happen on screen until the next pass of the runloop (i.e. once your method returns).
UIView will coalesce animations that are programmed sequentially.
Use the completion block to fade back out. The code looks a bit odd but it works great!
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[labelView setAlpha:1.0];
}
completion:^(BOOL completed){
[UIView animateWithDuration:5.8
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{[labelView setAlpha:0.0];}
completion:nil];
}];
In response to your comments:
The animations won't start until the next run of the runloop. They won't start until your app finishes what its doing. If you wait in the loop you will have the same problem and also freeze up your interface. Consider using individual labels for each letter, and add a progressively bigger delay for each animation. All these animation instructions will be queued up at once and then played out over the course of the next however many seconds. Imagine you are like a movie director, you tell each actor what to do in the next scene. Then, once everyone knows what to do you sit back and yell "action" and watch it all play out.

UIView chain block animation, delay is not working properly

UPDATE
: (I redid my functions so all the animations won't be as nested as before. Still no luck)
I have a piece of code where a try to, in this order:
Hide view A
Show view B
Show view C
The order is important!
The code is the following :
Main function:
[fileMenuController hide:0.2 andDelay:0.1];
[drawingToolController show:0.2 andDelay:0.2];
[penSizeMenuController showSubViewWithDuration:0.4];
fileMenuController hide function:
[UIView animateWithDuration:duration //begin animation
delay:delay
options:UIViewAnimationCurveEaseIn
animations:^{
[self.view setFrame:CGRectOffset([self.view frame], 0, -self.view.frame.size.height)];
}
completion:nil
];
drawingToolController show function:
[UIView animateWithDuration:duration //begin animation
delay:delay
options:UIViewAnimationCurveEaseIn
animations:^{
[self.view setFrame:CGRectOffset([self.view frame], 0, self.view.frame.size.height)];
}
completion:nil
];
penSizeController show function:
[UIView transitionWithView:self.view
duration:duration
options:UIViewAnimationOptionTransitionCurlDown
animations:^{ [self.view addSubview:subView] ;}
completion:nil];
self.view.alpha = 1;
My problem is the block penSizeController showSubView starts with the first animation (fileMenuController hide)!
The first two animations (fileMenuController hide and drawingToolController show) are working properly. When fileMenuController hide is done, drawingToolController starts.
So, does somebody know why the part in the penSizeController showSubView block starts at the same time as the first animation?
I'd imagine it's because the outer animation block doesn't have any animation – because the hide and show creates inner animation blocks – so it immediately calls the completion block.
Either remove the nested animation block in hide and show or add a parameter that disables animation for these nested animation actions.

How to have a handler to repeat UIView animateWithDuration?

I'm using UIView class method animateWithDuration for repeating my view animation. How can I have a handler that could be used to stop this animation later? For example, repeated animation starts in one method and I need to stop it later from another method.
You could do something like this assuming you have created a canceled property. As noted in the comments the completion block's startAnimation call needs to be wrapped in an async call to avoid a stack overflow. Be sure to replace the "id" with whatever class type you actually have.
- (void)startAnimation {
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void) {
//animate
}
completion:^(BOOL finished) {
if(!self.canceled) {
__weak id weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf startAnimation];
}];
}
}
];
}
The purpose of the animation is to repeatedly animate the bounce of an image. When there is no worry about manually stopping it then you just need to set three properties (UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat) and animation block code for moving the image - self.image.center = CGPointMake(self.image.center.x, self.image.center.y+25); Here is the full code of the animation:
[UIView animateWithDuration:0.5 delay:0 options:( UIViewAnimationOptionCurveEaseIn |
UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat |
UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center =
CGPointMake(self.image.center.x, self.image.center.y+25);} completion:nil];
That's it. But if you need a manual control then some additional code is required. First, according to jaminguy, you need to have a BOOL property for indication loop/stop (self.playAnimationForImage) the animation and clean separate method with animation code that would be called from elsewhere. Here is the method:
-(void)animateImageBounce{
[UIView animateWithDuration:0.5 delay:0 options:(
UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center =
CGPointMake(self.image.center.x, self.image.center.y+25);} completion:^(BOOL finished){if
(finished && self.playAnimationForImage){
self.image.center = CGPointMake(self.image.center.x, self.image.center.y-25);
[self animateImageBounce];
}}];
and here is the start of the animation call from some method
-(void)someMethod{
...
self.playAnimationForFingers = YES;
[self animateImageBounce];
}
The thing that I would like to note is that, in manual control, you need to reset the center.y of the image back right before next recursive call is performed.
Actually, the solution with recursive call didn't worked out for me. The animation started to behave weirdly: every 2- 3 animatation repeat cycle I got animation breaks. After the first bouncing part of item (moving item down) the second part (moving up) was performing almost instantly. I thing it has something to do with the recursive call.
Therefore, I refused to use that. The solution would be to start the animation with autoreverse and repeat options and, in complete block, to check if a flag (self.playAnimationForFingers) indicates to stop the animation.
-(void)animateFingersForLevelCompleteScreen{
//fix: reset the place of bouncing fingers (starting place).
//Otherwise the fingers will slowly move to the bottom at every level.
//This resetting is not working when placed inside UIView
//animate complete event block
self.image.center = CGPointMake(10 + self.image.frame.size.width/2,
95 + self.image.frame.size.height/2);
[UIView animateWithDuration:0.5 delay:0
options:(UIViewAnimationOptionCurveEaseIn |
UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAllowUserInteraction)
animations:^{
self.image.center = CGPointMake(self.image.center.x,
self.image.center.y+25);
}
completion:^(BOOL finished){
/*finished not approapriate: finished will not be TRUE if animation
was interrupted. It is very likely to happen because animation repeats && */
if (!self.playAnimationForFingers){
[UIView setAnimationRepeatAutoreverses:NO];
}
}];
}
U can make use of CABasicAnimation instead.
CABasicAnimation *appDeleteShakeAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
appDeleteShakeAnimation.autoreverses = YES;
appDeleteShakeAnimation.repeatDuration = HUGE_VALF;
appDeleteShakeAnimation.duration = 0.2;
appDeleteShakeAnimation.fromValue = [NSNumber numberWithFloat:-degreeToRadian(5)];
appDeleteShakeAnimation.toValue=[NSNumber numberWithFloat:degreeToRadian(5)];
[self.layer addAnimation:appDeleteShakeAnimation forKey:#"appDeleteShakeAnimation"];
Then when u want to stop it you can just call
[self.layer removeAnimationForKey:#"appDeleteShakeAnimation"];

Iphone flip views using block animation methods

Hi
I have looked without success for the answer to this.
Am trying to flip views on iphone app. Rather than using the usual iOS3 methods I want to use block methods to animate the transition.
Can anyone suggest a snippet of code to help please?
I tried the animateWithDuration method suggested above, and it didn't work. I couldn't get it working until I used the following:
[UIView transitionFromView:viewOld
toView:viewNew
duration:.75
options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationCurveEaseIn
completion:^(BOOL finished)
{
// cleanup viewOld
}
];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
// Exchange the views here
[view1 removeFromSuperview];
[mySuperview addSubview:view2];
}
completion:NULL];