8

Look at this video of the MLB At Bat app. Basically, I just want to present a modalViewController with the UIModalPresentationFormSheet style and have it grow from another view then flip. Like when you tap on a game in the scoreboard on the MLB app. Anyone know how I can accomplish this?

Thanks

EDIT: My main view is pretty much the same setup as the MLB app. I'm using AQGridView and want the animation to occur when a cell in the grid view is tapped.

EDIT 2: I'd also be open to ditching the UIViewController concept and just using a plain UIView, then replicate the style of UIModalPresentationFormSheet manually if that's easier.

EDIT 3: Okay, forget using a UIViewController to do this, since I haven't gotten any responses, I'll assume it isn't possible. My new question is just how do I replicate the animation in the posted video using just UIView's? So basically, the initial view needs to grow, move, and flip all at the same time.

EDIT 4: I think I have the actual animation figured out, now my only problem is calculating coordinates to feed into CATransform3DTranslate. My view needs to animate pretty much exactly like in the video. It needs to start over another view and animate to the center of the screen. Here's how I'm trying to calculate the coordinates for the view that pops up in the center:

CGPoint detailInitialPoint = [gridView convertPoint:view.frame.origin toView:detailView.frame.origin];
CGPoint detailFinalPoint = detailView.frame.origin;

gridView is the container view of my main view that holds the smaller grid items. view is the specific grid item that we are animating from. And detailView is the view that comes up in the middle of the screen.

edc1591
  • 10,146
  • 6
  • 40
  • 63

5 Answers5

6

You can implement your own transition using a category on UIViewControler.

UIViewController+ShowModalFromView.h

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

@interface UIViewController (ShowModalFromView)
- (void)presentModalViewController:(UIViewController *)modalViewController fromView:(UIView *)view;
@end

UIViewController+ShowModalFromView.m

#import "UIViewController+ShowModalFromView.h"

@implementation UIViewController (ShowModalFromView)

- (void)presentModalViewController:(UIViewController *)modalViewController fromView:(UIView *)view {
    modalViewController.modalPresentationStyle = UIModalPresentationFormSheet;

// Add the modal viewController but don't animate it. We will handle the animation manually
[self presentModalViewController:modalViewController animated:NO];

// Remove the shadow. It causes weird artifacts while animating the view.
CGColorRef originalShadowColor = modalViewController.view.superview.layer.shadowColor;
modalViewController.view.superview.layer.shadowColor = [[UIColor clearColor] CGColor];

// Save the original size of the viewController's view    
CGRect originalFrame = modalViewController.view.superview.frame;

// Set the frame to the one of the view we want to animate from
modalViewController.view.superview.frame = view.frame;

// Begin animation
[UIView animateWithDuration:1.0f
                 animations:^{
                     // Set the original frame back
                     modalViewController.view.superview.frame = originalFrame;
                 }
                 completion:^(BOOL finished) {
                     // Set the original shadow color back after the animation has finished
                     modalViewController.view.superview.layer.shadowColor = originalShadowColor;
                 }];
}

@end

This can easily be changed to use whatever animated transition you want; for your's, you might want to use a CA3DTransform. Hope this helps!

sgonzalez
  • 1,086
  • 9
  • 24
  • Where do I do my custom animations? Do I replace the `UIView` block animations with CoreAnimation animations? – edc1591 Apr 06 '12 at 12:13
  • Yes, replace the simple UIView animations with you more complex animations. I am not sure if the shadow does weird stuff with CoreAnimation, though. It should be easy to test either way. – sgonzalez Apr 06 '12 at 12:36
  • I can get the custom animation to work the way you have it, but I can't figure out how to combine this with a flip using the method here to achieve the flip http://stackoverflow.com/a/7463047/639668 – edc1591 Apr 06 '12 at 23:48
  • You can use the same method to return a 3D transform and copy over the rest of the code to act upon the viewController.view.layer as opposed to view2.layer. – sgonzalez Apr 07 '12 at 02:25
1

To do a flip, grow, and translate animation you can use the following code:

- (void)animate {
    int newX, newY; //New position
    int newW, newH; //New size
    [UIView animateWithDuration:someDuration delay:someDelay animations:^{
        yourView.transform = CATransform3DMakeRotation(M_PI_2,1.0,0.0,0.0); //flip halfway
        yourView.frame = CGRectMake(newX/2, newY/2, newW/2, newH/2);
    } completion:^{
        while ([yourView.subviews count] > 0)
            [[yourView.subviews lastObject] removeFromSuperview]; // remove all subviews
        // Add your new views here 
        [UIView animateWithDuration:someDuration delay:someDelay animations:^{
            yourView.transform = CATransform3DMakeRotation(M_PI,1.0,0.0,0.0); //finish the flip
            yourView.frame = CGRectMake(newX, newY, newW, newH);
        } completion:^{
            // Flip completion code here
        }];
    }];
}

Hope this helps!

sgonzalez
  • 1,086
  • 9
  • 24
  • 1
    I was hoping to do this using only `transform` and not `frame` so that my views subviews aren't distorted. – edc1591 Apr 15 '12 at 20:57
  • I know how to construct the transforms, but I can't figure out how to calculate the correct position for the layer. – edc1591 Apr 15 '12 at 20:59
  • I think that you can do pure `transform` using `CGAffineTransformConcat()` like here http://stackoverflow.com/questions/1111277/applying-multiple-transforms-to-a-uiview-calayer Where do you want the view to be displayed? – sgonzalez Apr 15 '12 at 21:06
  • OK, I figured out the animation, now the problem is calculating points. See my 4th edit on the original question. – edc1591 Apr 15 '12 at 21:23
  • Try using the `.center` property of UIView. Maybe `CGPoint detailFinalPoint = CGPointMake(gridView.center.x-detailView.frame.size.width/2, gridView.center.y-detailView.frame.size.height/2);` – sgonzalez Apr 16 '12 at 12:11
1

OK, I figured it out. Here's what I did:

CGFloat duration = 0.8;

/*
//Detail View Animations 
*/

CATransform3D initialDetailScale = CATransform3DMakeScale(view.frame.size.width/vc.view.frame.size.width, view.frame.size.height/vc.view.frame.size.height, 1.0);
CATransform3D initialDetailTransform = CATransform3DRotate(initialDetailScale, -M_PI, 0.0, -1.0, 0.0);
CATransform3D finalDetailScale = CATransform3DMakeScale(1.0, 1.0, 1.0);
CATransform3D finalDetailTransform = CATransform3DRotate(finalDetailScale, 0.0, 0.0, -1.0, 0.0);

NSMutableArray *detailAnimations = [NSMutableArray array];

CABasicAnimation *detailTransform = [CABasicAnimation animationWithKeyPath:@"transform"];
detailTransform.fromValue = [NSValue valueWithCATransform3D:initialDetailTransform];
detailTransform.toValue = [NSValue valueWithCATransform3D:finalDetailTransform];
detailTransform.duration = duration;
[detailAnimations addObject:detailTransform];

CABasicAnimation *detailFadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
detailFadeIn.fromValue = [NSNumber numberWithFloat:1.0];
detailFadeIn.toValue = [NSNumber numberWithFloat:1.0];
detailFadeIn.duration = duration/2;
detailFadeIn.beginTime = duration/2;
[detailAnimations addObject:detailFadeIn];

CABasicAnimation *detailMove = [CABasicAnimation animationWithKeyPath:@"position"];
detailMove.fromValue = [NSValue valueWithCGPoint:vc.view.layer.position];
detailMove.toValue = [NSValue valueWithCGPoint:self.view.layer.position];
detailMove.duration = duration;
[detailAnimations addObject:detailMove];

CAAnimationGroup *detailGroup = [CAAnimationGroup animation];
[detailGroup setAnimations:detailAnimations];
[detailGroup setDuration:duration];
detailGroup.removedOnCompletion = NO;
detailGroup.fillMode = kCAFillModeForwards;
detailGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[vc.view.layer addAnimation:detailGroup forKey:@"anim"];

/*
//Grid Item View Animations
*/

CATransform3D initialGridScale = CATransform3DMakeScale(1.0, 1.0, 1.0);
CATransform3D initialGridTransform = CATransform3DRotate(initialGridScale, 0.0, 0.0, 1.0, 0.0);
CATransform3D finalGridScale = CATransform3DMakeScale(vc.view.frame.size.width/view.frame.size.width, vc.view.frame.size.height/view.frame.size.height, 1.0);
CATransform3D finalGridTransform = CATransform3DRotate(finalGridScale, M_PI, 0.0, 1.0, 0.0);

NSMutableArray *gridAnimations = [NSMutableArray array];

CABasicAnimation *gridTransform = [CABasicAnimation animationWithKeyPath:@"transform"];
gridTransform.fromValue = [NSValue valueWithCATransform3D:initialGridTransform];
gridTransform.toValue = [NSValue valueWithCATransform3D:finalGridTransform];
gridTransform.duration = duration;
[gridAnimations addObject:gridTransform];

CABasicAnimation *gridFadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
gridFadeOut.fromValue = [NSNumber numberWithFloat:0.0];
gridFadeOut.toValue = [NSNumber numberWithFloat:0.0];
gridFadeOut.duration = duration/2;
gridFadeOut.beginTime = duration/2;
[gridAnimations addObject:gridFadeOut];

CABasicAnimation *gridMove = [CABasicAnimation animationWithKeyPath:@"position"];
gridMove.fromValue = [NSValue valueWithCGPoint:view.layer.position];
gridMove.toValue = [NSValue valueWithCGPoint:[self.view.layer convertPoint:self.view.layer.position toLayer:gridView.layer]];
gridMove.duration = duration;
[gridAnimations addObject:gridMove];

CAAnimationGroup *gridGroup = [CAAnimationGroup animation];
[gridGroup setAnimations:gridAnimations];
gridGroup.duration = duration;
gridGroup.fillMode = kCAFillModeForwards;
gridGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
gridGroup.removedOnCompletion = NO;
[view.layer addAnimation:gridGroup forKey:@"anim"];

I'm doing this with an item in an AQGridView. In my code sample view is the instance of AQGridViewCell that I am animating. And vc.view is my detail UIViewController/UIView that I'm displaying.

edc1591
  • 10,146
  • 6
  • 40
  • 63
  • I'm trying to get this to work for an iPhone app. What did you do with the detail view before running this code? Did you add the detail view controller's view to the heirarchy? If so, what frame was set? Currently when I run this code I get the half flip from the grid view but nothing appears for the detail view. – hundreth Jul 06 '12 at 18:22
  • @hundreth I added the detail view as a subview of the grid view and centered it. – edc1591 Jul 07 '12 at 03:47
0

I would highly recommend taking a look at this post.

You shouldn't need to actually handle all this animation yourself.

If you use the UIView class method transitionFromView:toView:duration:options:completion: and passing in UIViewAnimationOptionTransitionFlipFromLeft or UIViewAnimationOptionTransitionFlipFromRight -- whichever way you want the animation to flip.

I have implemented the same thing as shown in the MLB app using this method. Your from view would be the cell in the grid and the to view would be the thing that would on the "back" of the cell.

Hope this will reduce the overall code you'll need.

Community
  • 1
  • 1
njkremer
  • 269
  • 3
  • 11
  • I am trying a similar thing and struggling. How did you scale and move the view into position while it was flipping? – kyleplattner May 30 '13 at 15:17
  • It's been a while since I looked at this, but I think it would depend on the frames of the `fromView` and `toView` views that you pass into the `transitionFromView:toView:duration:options:completion:` method. – njkremer Jun 04 '13 at 21:30
0

I would use a UICollectionView with a custom UICollectionViewCell and animate in with its delegate call... Just an example for others to pick at.

#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:  
(NSIndexPath *)indexPath
{


[UIView beginAnimations:@"showImage" context:Nil];
    CGRect cellFrame = cell.frame;
    CGRect imgFram = cell.imageView.frame;
    [UIView setAnimationDuration:0.8];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:cell   
cache:YES];
    cellFrame.size = CGSizeMake(200, 200);
    cellFrame.origin.y = 10;
    cellFrame.origin.x = 45;
    cell.frame = cellFrame;
    imgFram.size = CGSizeMake(200, 200);
    cell.imageView.frame = imgFram;
    [collectionView bringSubviewToFront:cell];
    [UIView commitAnimations];
}
FreeAppl3
  • 858
  • 1
  • 15
  • 32