Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate between two states of a view, using custom animation

I would like to animate between two states of a view. Say, for example, I have a view with a label and when I change the text of the label an animation renders that change as a page flipping.

Now you can of course do this with a [UIView setAnimationTransition:forView:cache:]:

- (IBAction)nextPage {
    [UIView beginAnimation:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:pageView cache:NO];
    label.text = @"page 2";
    [UIView commitAnimations];
}

- (IBAction)previousPage {
    [UIView beginAnimation:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:pageView cache:NO];
    label.text = @"page 1";
    [UIView commitAnimations];
}

...but then you cannot use your own custom animation, you are stuck to the built-in animations (they're nice but they're not tailored to my needs).

So the other option is to add a CAAnimation to the view's layer:

- (IBAction)nextPage {
    CAAnimation *animation = [self animationWithDuration:1 forward:NO];
    [pageView.layer addAnimation:animation forKey:@"pageTransition"];
    label.text = @"page 2";
}

- (IBAction)previousPage {
    CAAnimation *animation = [self animationWithDuration:1 forward:YES];
    [pageView.layer addAnimation:animation forKey:@"pageTransition"];
    label.text = @"page 1";
}

Then you are free to set whatever animation Core Animation enables you to do. This works well if I define a CATransition animation, for example a kCATransitionReveal: a view in the "page 2" state appears below the view in the "page 1" state as it slips out.

- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
    CATransition *animation = [CATransition animation]; 
    [animation setType:kCATransitionReveal];
    [animation setSubtype:forward?kCATransitionFromLeft:kCATransitionFromRight];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];

    animation.duration = duration; 
    return animation; 
}

But when I define the animation to be for example a CABasicAnimation, only one state of the view is visible.

- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0 / -1000;
    transform = CATransform3DRotate(transform, M_PI, 0.0f, 1.0f, 0.0f);
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    if(forward) {
        animation.fromValue = [NSValue valueWithCATransform3D:transform];
    } else {
        animation.toValue = [NSValue valueWithCATransform3D:transform];
    }

    animation.duration = duration; 
    return animation; 
}

Instead, I would like the view in the "page 1" state to remain visible until the end of the animation while the view in the "page 2" state comes into frame, exactly as it behaves with a transition animation.

Of course, I could always mess with duplicating the view and having one appear as a sibling view while I animate the frontmost one and remove it from superview on completion... but there must be a much more straight way to achieve this rather simple animation effect without messing with the views.

Probably something to tell the layer, but then I don't know what, and that's where I need your help, guys :)

Thanks!

like image 998
KPM Avatar asked Jul 01 '10 12:07

KPM


1 Answers

I recently did a custom slide transition between subviews in a controller. My approach was to grab a bitmap of the view-out and the view-in, add them to a view and animate that view. At the end you can just remove the transition view and show the view-in. Here's a snippet the transition method:

-(void) transitionToView:(UIView *)viewIn lastView:(UIView *)viewOut slideRight:(BOOL)isRight { 
UIGraphicsBeginImageContext(viewOut.frame.size);

UIImageView *bitmapOut = [[UIImageView alloc] initWithFrame: viewOut.frame];

[viewOut.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewOutImage = UIGraphicsGetImageFromCurrentImageContext();   
UIGraphicsEndImageContext();
bitmapOut.image = viewOutImage;

UIImageView *bitmapIn = [[UIImageView alloc] initWithFrame: viewIn.frame ];
bitmapIn.frame = CGRectMake(isRight ? 320 : -320, bitmapIn.frame.origin.y, bitmapIn.frame.size.width, bitmapIn.frame.size.height);
UIGraphicsBeginImageContext(viewIn.frame.size);

[viewIn.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewInImage = UIGraphicsGetImageFromCurrentImageContext();   
UIGraphicsEndImageContext();

bitmapIn.image = viewInImage;

[self.view addSubview:transitionContainer];

[transitionContainer addSubview:bitmapIn];
[transitionContainer addSubview:bitmapOut];

[self removeAllViews];

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(swapViewsAtEndOfTransition:)];
transitionContainer.frame = CGRectMake(isRight ? -320 : 320, 0, 640, 411);
[UIView commitAnimations];

[bitmapOut release]; bitmapOut = nil;
[bitmapIn release]; bitmapIn = nil;}

Then, in the swapViewsAtEndOfTransition selector you can update the views accordingly.

like image 193
ezekielDFM Avatar answered Oct 04 '22 20:10

ezekielDFM