Skip to content

Instantly share code, notes, and snippets.

@cbess
Created May 8, 2014 17:38
Show Gist options
  • Save cbess/14515d969dc06c8220cf to your computer and use it in GitHub Desktop.
Save cbess/14515d969dc06c8220cf to your computer and use it in GitHub Desktop.
iOS UIView flip examples
@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