I'm flipping UIViews similar to the page flip of the Weather app. The views are not fullscreen, though, and the superview has rounded corners. The problem is that during the flip animation the rounded corners of the superview are filled out to the square corners with black.
Here's how I'm setting the corners:
self.view.layer.cornerRadius = 15.0f;
self.view.clipsToBounds = YES;
Here's how I'm flipping the views (both frontView
and backView
are retained):
UIView *toView;
UIView *fromView;
UIViewAnimationOptions animationType;
if (toFront) {
toView = self.frontView;
fromView = self.backView;
animationType = UIViewAnimationOptionTransitionFlipFromLeft;
} else {
toView = self.backView;
fromView = self.frontView;
animationType = UIViewAnimationOptionTransitionFlipFromRight;
}
[UIView transitionFromView:self.fromView
toView:self.toView
duration:1
options:animationType
completion:nil];
When I do this, self.view
's round corners are filled out to their rectangular corner edges with black. Can this be avoided? I don't think I know enough about Core Animation to solve this.
If you need to do this in Core Animation, you would do it by making the animation separately from replacing the views. It's not impossibly difficult. The description of how to do it and some sample code is below:
Replacing the views
Replacing the views can for example be done using:
[UIView transitionFromView:self.fromView
toView:self.toView
duration:0.0
options:UIViewAnimationOptionTransitionNone
completion:nil];
Animating the flip
The difficult part is obviously the animation (and putting both together if you've never done it before).
Only showing the front of both views
Since you want to flip the two views as if they are two sides of a piece of paper you don't want either of them to appear when they are facing away. This is done by setting the doubleSided
property to NO
on both layers. (not being double sided means being single sided).
Making the flip
You want to animate the transform of the front view from a identity transform (no transform) to a 180 degree rotated transform (facing backwards) around the y-axis so that it flips form left to right.
At the same time you want to animate the transform of the back view from a -180 degree rotation to an identity transform. This will make flip back. Both of these animations can be done as "basic" animations:
CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
[flipAnimation setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]]; // No rotation
[flipAnimation setToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0,1,0)]]; // Rotate PI around Y
[flipAnimation setFillMode:kCAFillModeBoth];
[flipAnimation setRemoveOnCompletion:NO];
[flippingView layer] addAnimation:flipAnimation forKey:@"myFlipAnimation"];
You then do the same kind of animation for the other view. (The fill mode prevents the animation from jumping back to its original state after completion)
Putting it all together
Since you want both animations to run at the same time and want your view-replacing code to run when the animation finishes you can use a animation transaction. This will also allow you to configure how both animations look at one place:
[CATransaction begin];
[CATransaction setAnimationDuration:2.0]; // 2 seconds
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[CATransaction setCompletionBlock:^{
// Your view replacing code here...
}];
// Add the animations to both views here...
[CATransaction commit];
Here's what I ended up writing. Seems to work fine, but, of course, test it yourself.
Things to note:
You should add the view you are animating TO before calling this
method.
The method does NOT remove any of the views, so remove them
with the completion block if necessary.
The method also does not
modify the 'layer model' values of your views, so they will stay the same as
they were after the animation.
`
+ (void)flipRight:(BOOL)flipRight fromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration completion:(void (^)(void))completionBlock {
// Save original values
BOOL fromViewDoubleSided = fromView.layer.doubleSided;
BOOL toViewDoubleSided = toView.layer.doubleSided;
BOOL fromViewUserInteractionEnabled = fromView.userInteractionEnabled;
BOOL toViewUserInteractionEnabled = toView.userInteractionEnabled;
// We don't want to see the other side of the layer
fromView.layer.doubleSided = NO;
toView.layer.doubleSided = NO;
// We don't want user to fire off animation while its already going
fromView.userInteractionEnabled = NO;
toView.userInteractionEnabled = NO;
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// Restore original values
fromView.layer.doubleSided = fromViewDoubleSided;
toView.layer.doubleSided = toViewDoubleSided;
fromView.userInteractionEnabled = fromViewUserInteractionEnabled;
toView.userInteractionEnabled = toViewUserInteractionEnabled;
if (completionBlock) completionBlock();
}];
CATransform3D flipOutTransform3D = CATransform3DMakeRotation(M_PI, 0,1,0);
flipOutTransform3D.m34 = 1.0/(300.0*(flipRight ? -1 : 1));
CABasicAnimation *flipOutAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
flipOutAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
flipOutAnimation.toValue = [NSValue valueWithCATransform3D:flipOutTransform3D];
flipOutAnimation.duration = duration;
CATransform3D flipInTransform3D = CATransform3DMakeRotation(M_PI, 0,1,0);
flipInTransform3D.m34 = 1.0/(300.0*(flipRight ? 1 : -1));
CABasicAnimation *flipInAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
flipInAnimation.fromValue = [NSValue valueWithCATransform3D:flipInTransform3D];
flipInAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
flipInAnimation.duration = duration;
[fromView.layer addAnimation:flipOutAnimation forKey:@"flipOutAnimation"];
[toView.layer addAnimation:flipInAnimation forKey:@"flipInAnimation"];
[CATransaction commit];
};