Created
May 8, 2014 17:38
-
-
Save cbess/14515d969dc06c8220cf to your computer and use it in GitHub Desktop.
iOS UIView flip examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@interface FNViewController () { | |
BOOL isFlipped; | |
BOOL isTransitioning; | |
CALayer *topLayer; | |
CALayer *bottomLayer; | |
} | |
@property (weak, nonatomic) IBOutlet UIImageView *oneImageView; | |
@property (weak, nonatomic) IBOutlet UIImageView *twoImageView; | |
@property (nonatomic, assign) BOOL displayingFront; | |
@property (nonatomic, weak) IBOutlet UIView *containerView; | |
@end | |
@implementation FNViewController | |
- (void)viewDidLoad { | |
[super viewDidLoad]; | |
self.displayingFront = YES; | |
topLayer = [CALayer layer]; | |
topLayer.doubleSided = NO; | |
topLayer.name = @"1"; | |
topLayer.frame = CGRectMake(0.0f, 0.0f, 125.0f, 125.0f); | |
topLayer.position = CGPointMake(160.0f, 240.0f); | |
topLayer.backgroundColor = [[UIColor brownColor] CGColor]; | |
topLayer.cornerRadius = 8.f; | |
topLayer.masksToBounds = YES; | |
bottomLayer = [CALayer layer]; | |
bottomLayer.doubleSided = topLayer.doubleSided; | |
bottomLayer.name = @"2"; | |
bottomLayer.frame = topLayer.frame; | |
bottomLayer.position = topLayer.position; | |
bottomLayer.backgroundColor = [[UIColor greenColor] CGColor]; | |
bottomLayer.cornerRadius = topLayer.cornerRadius; | |
bottomLayer.masksToBounds = topLayer.masksToBounds; | |
// FIX for flipping over an image | |
// Assuming this is over a background UIImageView (optional for folks) | |
// UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Space-03.jpg"]]; | |
// add this as the root of the current view; you can do this in IB | |
// [self.view insertSubview:imageView atIndex:0]; | |
// now add the flipping layers to a different view, above the UI Image, but below the controls | |
// defined in the XIB | |
UIView *flipView = [[UIView alloc] initWithFrame:self.view.frame]; | |
flipView.userInteractionEnabled = NO; | |
[self.view addSubview:flipView]; | |
// add bottom first; add to the NEW view, not self | |
[flipView.layer addSublayer:bottomLayer]; | |
[flipView.layer addSublayer:topLayer]; | |
} | |
- (IBAction)flipButtonPressed:(id)sender { | |
if (self.displayingFront) { | |
[self flipFromFront:self.oneImageView toBack:self.twoImageView]; | |
} else { | |
[self flipFromBack:self.twoImageView toFront:self.oneImageView]; | |
} | |
return; | |
// if we currently transitioning, ignore any further taps on the button until we are done | |
if (isTransitioning) | |
return; | |
// figure out which layer is the current visible one | |
CALayer *top = topLayer; | |
CALayer *bot = bottomLayer; | |
if (isFlipped) { | |
top = bottomLayer; | |
bot = topLayer; | |
} | |
const float scaleFactor = 1.f; | |
CAAnimation *topAnimation = [self flipAnimationWithDuration:1.0f forLayerBeginningOnTop:YES scaleFactor:scaleFactor]; | |
CAAnimation *bottomAnimation = [self flipAnimationWithDuration:1.0f forLayerBeginningOnTop:NO scaleFactor:scaleFactor * 3]; | |
CGFloat zDistance = 1500.0f; | |
CATransform3D perspective = CATransform3DIdentity; | |
perspective.m34 = -1.f / zDistance; | |
top.transform = perspective; | |
bot.transform = perspective; | |
topAnimation.delegate = self; | |
[CATransaction begin]; | |
[top addAnimation:topAnimation forKey:@"flip"]; | |
[bot addAnimation:bottomAnimation forKey:@"flip"]; | |
[CATransaction commit]; | |
} | |
- (CAAnimation *)flipAnimationWithDuration:(NSTimeInterval)aDuration forLayerBeginningOnTop:(BOOL)beginsOnTop scaleFactor:(CGFloat)scaleFactor { | |
isTransitioning = YES; | |
// Rotating halfway (pi radians) around the Y axis gives the appearance of flipping | |
CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"]; | |
CGFloat startValue = beginsOnTop ? 0.0f : M_PI; | |
CGFloat endValue = beginsOnTop ? -M_PI : 0.0f; | |
flipAnimation.fromValue = @(startValue); | |
flipAnimation.toValue = @(endValue); | |
// Shrinking the view makes it seem to move away from us, for a more natural effect | |
// Can also grow the view to make it move out of the screen | |
CABasicAnimation *shrinkAnimation = nil; | |
if (scaleFactor != 1.0f) { | |
shrinkAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; | |
shrinkAnimation.toValue = @(scaleFactor); | |
// We only have to animate the shrink in one direction, then use autoreverse to "grow" | |
shrinkAnimation.duration = aDuration * 0.5; | |
shrinkAnimation.autoreverses = YES; | |
} | |
// Combine the flipping and shrinking into one smooth animation | |
CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; | |
animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, shrinkAnimation, nil]; | |
// As the edge gets closer to us, it appears to move faster. Simulate this in 2D with an easing function | |
// animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; | |
animationGroup.duration = aDuration; | |
// this really means keep the state of the object at whatever the anim ends at | |
// if you don't do this then it reverts back to the original state (e.g. brown layer) | |
animationGroup.fillMode = kCAFillModeForwards; | |
animationGroup.removedOnCompletion = NO; | |
return animationGroup; | |
} | |
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { | |
isFlipped = !isFlipped; | |
isTransitioning = NO; | |
} | |
// flip and scale frontView to reveal backView to the center of the screen | |
// uses a containerView to mark the end of the animation | |
// parameterizing the destination is an exercise for the reader | |
- (void)flipFromFront:(UIView*)frontView toBack:(UIView*)backView | |
{ | |
float duration = 0.5; | |
// distance from center of screen from frontView | |
float dx = self.view.center.x - frontView.center.x; | |
float dy = self.view.center.y - frontView.center.y; | |
// this prevents any tearing | |
backView.layer.zPosition = 200.0; | |
backView.alpha = 0.0; | |
backView.frame = frontView.frame; | |
// start the animation | |
[UIView animateKeyframesWithDuration:duration | |
delay:0.25 | |
options:UIViewKeyframeAnimationOptionCalculationModeCubic | |
animations:^{ | |
// part 1. Rotate and scale frontView halfWay. | |
[UIView addKeyframeWithRelativeStartTime:0.0 | |
relativeDuration:0.5 | |
animations:^{ | |
// get the transform for the blue layer | |
CATransform3D xform = frontView.layer.transform; | |
// translate half way | |
xform = CATransform3DTranslate(xform, dx/2, dy/2, 0); | |
// rotate half way | |
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0); | |
// scale half way | |
xform = CATransform3DScale(xform, 1.5, 1.5, 1); | |
// apply the transform | |
frontView.layer.transform = xform; | |
}]; | |
// part 2. set the backView transform to frontView so they are in the same | |
// position. | |
[UIView addKeyframeWithRelativeStartTime:0.5 | |
relativeDuration:0.0 | |
animations:^{ | |
backView.layer.transform = frontView.layer.transform; | |
backView.alpha = 1.0; | |
}]; | |
// part 3. rotate and scale backView into center of container | |
[UIView addKeyframeWithRelativeStartTime:0.5 | |
relativeDuration:0.5 | |
animations:^{ | |
// undo previous transforms with animation | |
backView.layer.transform = CATransform3DIdentity; | |
// animate backView into new location | |
backView.center = self.containerView.center; | |
}]; | |
} completion:^(BOOL finished) { | |
self.displayingFront = !self.displayingFront; | |
}]; | |
} | |
// flip from back to front | |
- (void) flipFromBack:(UIView*)backView toFront:(UIView*)frontView | |
{ | |
float duration = 0.5; | |
// get distance from center of screen to destination | |
float dx = self.view.center.x - frontView.center.x; | |
float dy = self.view.center.y - frontView.center.y; | |
backView.layer.zPosition = 200.0; | |
frontView.hidden = YES; | |
// this is basically the reverse of the previous animation | |
[UIView animateKeyframesWithDuration:duration | |
delay:0 | |
options:UIViewKeyframeAnimationOptionCalculationModeCubic | |
animations:^{ | |
[UIView addKeyframeWithRelativeStartTime:0.0 | |
relativeDuration:0.5 | |
animations:^{ | |
CATransform3D xform = backView.layer.transform; | |
xform = CATransform3DTranslate(xform, -dx/2, -dy/2, 0); | |
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0); | |
xform = CATransform3DScale(xform, 0.75, 0.75, 1); | |
backView.layer.transform = xform; | |
}]; | |
[UIView addKeyframeWithRelativeStartTime:0.5 | |
relativeDuration:0.0 | |
animations:^{ | |
backView.alpha = 0.0; | |
frontView.hidden = NO; | |
}]; | |
[UIView addKeyframeWithRelativeStartTime:0.5 | |
relativeDuration:0.5 | |
animations:^{ | |
// self.hiddenView.alpha = 0.0; | |
frontView.layer.transform = CATransform3DIdentity; | |
}]; | |
} completion:^(BOOL finished) { | |
self.displayingFront = !self.displayingFront; | |
}]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment