6

I have two views "A" and "B". A floats in the middle of the window (it's not full screen). B, the view which will replace A, is full screen. I'd like to write a custom transition that flips A to reveal B on the back side, but simultaneously scales the flipping rectangle so that when the flip is finished, B is full screen.

I've tried using the flip transitions available with transitionFromView:toView:duration:options:completion, but I can only get it to flip the entire screen instead of starting the flip with A's frame and ending with B's frame.

I know I can do 3D-like transforms to the view's layers, but I'm not sure which set of animation APIs I should use to accomplish this. One thing I tried is to modify the view's layer properties in the animations block of transitionWithView:duration:options:animations:completion: but that didn't work as it only seems to honor view property modifications.

Can someone point me in the right direction? I'd much appreciate it.

UPDATE: Here is my code thus far for this effect. You can see a video of what it does here: http://www.youtube.com/watch?v=-xNMD2fGRwg

CGRect frame = [[UIApplication easybookDelegate] rootViewController].view.bounds ;
float statusHeight = [UIApplication sharedApplication].statusBarFrame.size.height ;
frame.origin.y = statusHeight ;
frame.size.height -= statusHeight ;
self.view.frame = frame ; // self.view is view "B"

// Put the snapshot as thet topmost view of our view
UIImageView *imageView = [[UIImageView alloc] initWithImage:image] ; // image is a snapshot of view "A"
imageView.frame = self.view.bounds ;
[self.view addSubview:imageView] ;
[self.view bringSubviewToFront:imageView] ;

// Pre-transform our view (s=target/current, d=target-current)
CGAffineTransform transform = CGAffineTransformIdentity ;

// Translate our view
CGPoint center  = CGPointMake(screenOrigin.x + (image.size.width/2.0), screenOrigin.y + (image.size.height/2.0)) ;
float dX = center.x - self.view.center.x ;
float dY = center.y - self.view.center.y ;
NSLog( @"dx: %f, dy: %f" , dX, dY ) ;

transform = CGAffineTransformTranslate(transform, dX, dY) ;

// Scale our view
float scaleWFactor = image.size.width / self.view.frame.size.width ;
float scaleHFactor = image.size.height / self.view.frame.size.height ;
transform = CGAffineTransformScale(transform,scaleWFactor, scaleHFactor) ;

self.view.transform = transform ;

[[[UIApplication easybookDelegate] rootViewController].view addSubview:self.view] ;

// Perform the animation later since implicit animations don't seem to work due to
// view "B" just being added above and hasn't had a chance to become visible. Right now
// this is just on a timer for debugging purposes. It'll probably be moved elsewhere, probably
// to view "B"'s -didMoveToSuperview
double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [UIView transitionWithView:self.view duration:3 options:UIViewAnimationOptionTransitionFlipFromRight| UIViewAnimationOptionCurveEaseOut animations:^(void) 
        {
        [imageView removeFromSuperview] ;
        self.view.transform = CGAffineTransformIdentity ;
        } 
        completion:^(BOOL finished){
                [self.view removeFromSuperview] ;
                [navController presentModalViewController:self animated:NO] ;
        }] ;
});

[imageView release];

leftspin
  • 2,468
  • 1
  • 25
  • 40
  • 1
    Youtube account deleted( – k06a Jul 11 '12 at 17:39
  • Here's some code I've written since I originally posted this that can perform the effect (I've used it to do so in several apps): https://github.com/leftspin/IVM-Awesomesauce – leftspin Aug 27 '12 at 03:32

6 Answers6

3

I've done this before. Here's the gist of how I did it:

  1. View A is on screen
  2. Create View B, give it a frame that makes it fullscreen, but don't add it to the screen yet
  3. View B has a UIImageView as it's topmost view
  4. Capture a UIImage of View A, and set it as the image of View B's image view

    UIGraphicsBeginImageContext(view.bounds.size); 
    [viewA.layer renderInContext:UIGraphicsGetCurrentContext()]; 
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    viewB.imageView.image = image;
    
  5. Set View B's transform (scale and translation) so that it is shrunk to the size of View A and is positioned where View A is on the screen

  6. Add View B to the screen
  7. At this point, the user hasn't noticed anything change, because View B looks exactly like View A
  8. Now start an animation block, in which you
    • Set [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:ViewB
    • set View B's transform to CGAffineTransformIdentity
    • remove View B's imageview from View B

The result is an animation that looks like View A is flipping over and zooming to fill the screen with View B as the opposite side.

Gray
  • 115,027
  • 24
  • 293
  • 354
CharlieMezak
  • 5,999
  • 1
  • 38
  • 54
  • Wouldn't this simply just scale the view? I don't see where the flip happens here… – leftspin Apr 15 '11 at 03:50
  • Actually, it doesn't look like you can set a view's transform to do a 3D flip since it's a 2D transform matrix. – leftspin Apr 15 '11 at 05:46
  • You do the flip by setting the animationTransition value for the animation. I'll edit the answer… – CharlieMezak Apr 18 '11 at 18:40
  • Sort of works. Some notes: there was no flip animation because view B had not appeared yet before removing B's imageView. I had to add view B then schedule the transform and imageView removal to happen later. Also, the image used in the "front" part of the flip doesn't scale while flipping, just the "back" part of the flip; there's a scaling discontinuity when flipping from front to back. I fixed this by applying the scaling transform after the flip animation so that the effect is of a fixed-size card flipping, and then the card subsequently zooming to full screen. – leftspin Apr 20 '11 at 21:37
  • BTW, this helped a lot so I'm assigning you the bounty, but I would still love to get it so that the "front" part of the animation scales along with the "back" side. – leftspin Apr 20 '11 at 21:38
  • Thanks for the bounty. I just gave away 100 points today myself! Can you post your animation code in your question so we can take a look at it? We can probably get you the rest of the way. – CharlieMezak Apr 20 '11 at 23:51
  • Hi Charlie, I posted the code above along with a video of what it does. I have switched to trying this using a pure layer-based implementation so I can have full control over the animation (with some cool 3D effects), but I'd still love to know if the view-based version can be fixed. Thanks again for your help! – leftspin Apr 21 '11 at 19:28
  • I see that you're using blocks to handle the animations. That's probably what you're supposed to do these days, but when I did it successfully, I used the old beginAnimations/commitAnimations messages. I'll go back and find my code and see what I can turn up. – CharlieMezak Apr 23 '11 at 16:18
1

Checkout this svn repository:

http://boondoggle.atomicwang.org/lemurflip/

This does flip two views.

Nick Weaver
  • 47,228
  • 12
  • 98
  • 108
1

This is an easy example that i made for you :-) Start with it... Try to understand it... Read Documentation and then you will be able to do what you want ;-)

- (void)moveTable:(UITableView *)table toPoint:(CGPoint)point excursion:(CGFloat)excursion {

[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5];
[table setCenter:point];

// scaling
CABasicAnimation *scalingAnimation = (CABasicAnimation *)[table.layer animationForKey:@"scaling"];
if (!scalingAnimation)
{
    scalingAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    scalingAnimation.duration=0.5/2.0f;
    scalingAnimation.autoreverses=YES;
    scalingAnimation.removedOnCompletion = YES;
    scalingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    scalingAnimation.fromValue=[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0)];
    scalingAnimation.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(excursion, 0.0, 0.0)];
}
[table.layer addAnimation:scalingAnimation forKey:@"scaling"];
[UIView commitAnimations];

}

Marco
  • 11
  • 2
  • Why are you assigning a translation animation to the "scaling" key, and what does the "scaling" key have to do with translating the view? – leftspin Apr 15 '11 at 05:51
0

Try something like this. As long as A's frame isn't fullscreen to start with then the animation should just flip A. When you add a subview to a view in a flip transition then it is added to the back of that view, so view A's frame is the one you want to modify.

[UIView beginAnimations:@"flipopen" context:nil];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self cache:TRUE];
[a addSubview:b];
a.frame = b.frame;
[UIView commitAnimations];
Casey
  • 2,393
  • 1
  • 20
  • 22
0

I'm assuming you just want to change the image view's image while you flip and scale it. Following code flips the image view using default animation, scales it to the other view's frame and sets the image:

[UIView beginAnimations:nil context:nil];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.imageViewA cache:NO];
[UIView setAnimationDuration:2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationFinished:)];
self.imageViewA.frame = self.imageViewB.frame;
self.imageViewA.image = self.imageViewB.image;
[UIView commitAnimations];

if you wish to show the A view as your tiny floating view again with some other image you can just set it's frame and image without the animation block in AnimationDidStopSelector method which you can set

-(void)animationFinished:(id)context
{
    self.imageViewA.frame = tinyRect;
    self.imageViewA.image = [UIImage imageNamed:@"a.jpg"];
}

where tinyRect is the floating A view's original frame.

Swapnil Luktuke
  • 10,385
  • 2
  • 35
  • 58
0

It's a little rough around the edges, but here's what I did using block animations. The following flips the view, centers it and goes fullscreen:

// the view on the other side of the card
UIView* redView = [[UIView alloc]initWithFrame:CGRectMake(0.0, 0.0, viewToFlip.frame.size.width, viewToFlip.frame.size.height)];
redView.backgroundColor = [UIColor redColor];

// add a green square to the red view so we have something else to look at
UIView* greenSquare = [[UIView alloc]initWithFrame:CGRectMake(10.0, 10.0, 20.0, 20.0)];
greenSquare.backgroundColor = [UIColor greenColor];    
[redView addSubview:greenSquare];

// the flip animation
[UIView animateWithDuration:0.25
                      delay:0.0
                    options:UIViewAnimationCurveLinear
                 animations:^{

                     // rotate 90 degrees
                     [viewToFlip.layer setTransform: CATransform3DRotate(viewToFlip.layer.transform, -M_PI/2, 0.0, 1.0, 0.0)];

                 } 
                 completion:^(BOOL finished){

                     // now that we're 90 degrees, swap one view for the other
                     // but if we add it now, the view will be backwards, so flip the original view back
                     [viewToFlip.layer setTransform: CATransform3DRotate(viewToFlip.layer.transform, M_PI, 0.0, 1.0, 0.0)];
                     [viewToFlip addSubview:redView];

                     // now let's continue our rotation
                     [UIView animateWithDuration:0.25
                                           delay:0.0
                                         options:UIViewAnimationCurveLinear
                                      animations:^{

                                          // rotate the last 90 degrees
                                          [viewToFlip.layer setTransform: CATransform3DRotate(viewToFlip.layer.transform, -M_PI/2, 0.0, 1.0, 0.0)];

                                      } 
                                      completion:^(BOOL finished){

                                          [UIView animateWithDuration:0.25
                                                                delay:0.0
                                                              options:UIViewAnimationCurveLinear
                                                           animations:^{

                                                               // animate the views to the center of the screen and adjust their bounds to fill up the screen
                                                               [viewToFlip setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
                                                               [viewToFlip setBounds:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
                                                               [redView setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
                                                               [redView setBounds:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
                                                           }
                                                           completion:nil];


                                      }];

                 }];
aoakenfo
  • 914
  • 1
  • 7
  • 9