views:

2384

answers:

2

Hi--

I'm seeing what appears to be interaction between separate animations, and I'd really appreciate any suggestions to eliminate this effect.

Basically: I've got an iPhone app which includes a button 'a' on the root view. Tapping 'a' pushes a view on a navigation view controller stack, with a flip animation. The pushed view has a button to pop the view back to the root.

The underlying code:

- (IBAction)pushOneView{
TheAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight 
                    forView:delegate.navigationController.view cache:NO];
[delegate.navigationController 
            pushViewController:oneViewController animated:NO];
[UIView commitAnimations];

}

This seems to work fine, and the animation is quite smooth.

The root view also includes a subview ('panelView'), and another button, 'b'. panelView can display either of two other subviews--tapping 'b' swaps between those subviews, with a spin animation. The code:

-(IBAction)swapPanels{
UIViewController *coming;
UIViewController *going;
float rotation;

if (self.aPanel.view.superview == nil) {
    coming = aPanel;
    going = bPanel;
    rotation = 3.14;
}
else {
    coming = bPanel;
    going = aPanel;
    rotation = -3.14;
}

// First half of spin
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
CGAffineTransform swirlTransform = CGAffineTransformMakeRotation(rotation);
panelView.transform = swirlTransform;
[panelView setAlpha:0.1];
[UIView commitAnimations];

// Finish spin
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
CGAffineTransform secondTransform = 
                        CGAffineTransformRotate(swirlTransform, rotation);
panelView.transform = secondTransform;
[panelView setAlpha:1];
[UIView commitAnimations];

// Swap the panels
[going.view removeFromSuperview];
[panelView insertSubview:coming.view atIndex:0];

}

This also seems to work fine. Each of the swappable panels contains a picker and some labels.

However, I notice that the 'a' transition becomes slow and jerky if the 'b' transition has been executed before it. In other words, if I start the app, run 'a' back and forth several times, it runs smoothly. Then exercise 'b' back and forth a few times. Then try 'a' again...'a' is now jerky, and will remain so until an app restart.

This is 100% repeatable. It is subtle using the simulator, but quite obvious on a device. I have tested for leaks--none are shown by leaks tool. If the animation is removed from the 'b' operation (just comment-out the animation steps), the effect on 'a' is not observed after the 'b' subview swap is exercised. If the pickers are removed from the swappable panel nibs, the effect is similarly eliminated. If the 'a' animation transition is set to cache, then after 'b' it does not stutter in the middle, but seems to ignore animating, simply swapping the view (this may be a matter of perception).

In case I'm not clear: I am NOT triggering these separate operations at the same time. Animation 'a', after 'b' has been executed--and completed--is not the same as if 'b' had never been executed. Is there clean-up I should be doing after an animation? Is my subview-swapping code flawed? Or...?

Thanks in advance for any suggestions.

+5  A: 

You do have two overlapping animations in your 'b' section. When you create an animation using a begin / commit block, it gets handed off to a background thread to perform. In your 'b' section, the two animations that should be sequential are actually being fired off at nearly the same time. This can cause bizarre behavior, possibly like what you are seeing.

I'd recommend adding a callback in your first animation to a delegate method using code like the following:

[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(rotationAnimationFinished:finished:context:)];

within your first animation block in 'b' (the "First half of spin" part). Then you need to define the callback method within your class, in this case

- (void)rotationAnimationFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context;

Within that delegate method, which will be called when the first part of the spin animation has finished, place the remaining code from your 'b' method (everything after your "Finish spin" comment).

By separating apart these animations, that might prevent the weirdness you're seeing.

Brad Larson
Thanks much for the suggestion, Brad, but I don't think we're there yet. While I understand your point, one test of it is to eliminate the second half of the 'b' animation entirely...and doing so makes no difference in the behavior of the 'a' animation.
jpb
A: 

Brad's answer does seem to have lead me to a solution: the symptoms continued to suggest that something about running those 'b' animations left the system in a different state than when it started. Finally, it occurred to me that the transform property of the panelView was left set after the 'b' animations...and perhaps that impacted further animations of a superview containing panelView.

So I set up method as Brad suggested, to execute once the 'b' animations were complete--but all that method does is set the panelView transform back to the default: CGAffineTransformIdentity:

  • (void)spinFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context { panelView.transform = CGAffineTransformIdentity; }

With this in place, post-'b' executions of the 'a' animation appear to be back to normal. So thanks again, Brad, for your help here. It is much appreciated.

jpb