Reputation: 2121
I'm trying to recreate Apple's UIView transitionFromView:ToView:
with Core Animation so I can use it with an interactive view controller transition. It's almost there, but I can't get the "to" view controller's view to show up on the back of the card.
CATransform3D inRotation = CATransform3DMakeRotation(M_PI, 0.0, 1.0, 0.0);
inRotation.m34 = -0.02;
CATransform3D outRotation = CATransform3DMakeRotation(-0.01, 0.0, 1.0, 0.0);
outRotation.m34 = -0.02;
[UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
fromViewController.view.layer.transform = inRotation;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
toViewController.view.layer.transform = outRotation;
}];
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
I think I need to swap the two views (fromViewController.view
and toViewController.view
) somehow halfway through the transition but I can't seem to find a solution anywhere.
Upvotes: 1
Views: 1099
Reputation: 38667
Your inRotation
should have been with M_PI_2
(or -M_PI_2
) instead of M_PI
. The following solution is closer to your original code and it avoids your hacky workaround of just over or just short of pi by performing two quarter rotations:
func flip(_ flipToFront: Bool) {
let fromView: UIView = flipToFront ? toViewController.view! : fromViewController.view!
let toView: UIView = flipToFront ? fromViewController.view! : toViewController.view!
fromView.superview!.insertSubview(fromView, belowSubview: toView)
var initialToRotation: CATransform3D = CATransform3DMakeRotation(.pi, 0, 1, 0)
initialToRotation.m34 = -0.004
toView.layer.transform = initialToRotation
var initialFromRotation: CATransform3D = CATransform3DIdentity
initialFromRotation.m34 = -0.004
fromView.layer.transform = initialFromRotation
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
fromView.layer.transform = CATransform3DRotate(initialFromRotation, .pi / (flipToFront ? 2 : -2), 0, 1, 0)
toView.layer.transform = CATransform3DRotate(initialToRotation, .pi / (flipToFront ? -2 : 2), 0, 1, 0)
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: {
fromView.layer.transform = CATransform3DRotate(initialFromRotation, .pi, 0, 1, 0)
toView.layer.transform = CATransform3DRotate(initialToRotation, .pi, 0, 1, 0)
})
}, completion: nil)
}
Where I previously set:
override func viewDidLoad() {
super.viewDidLoad()
fromViewController.view.layer.isDoubleSided = false
toViewController.view.layer.isDoubleSided = false
}
Upvotes: 0
Reputation: 2121
I'm not sure if this is the most idiomatic way to do this but I have a working solution:
[fromViewController.view.layer setDoubleSided:NO];
[toViewController.view.layer setDoubleSided:NO];
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
CGFloat h = 0.0001;
CATransform3D initialToRotation = CATransform3DMakeRotation(M_PI - h, 0.0, 1.0, 0.0);
initialToRotation.m34 = -0.002;
[toViewController.view.layer setTransform:initialToRotation];
CATransform3D initialFromRotation = CATransform3DIdentity;
initialFromRotation.m34 = -0.002;
[fromViewController.view.layer setTransform:initialFromRotation];
[UIView animateWithDuration:0.5 animations:^{
[fromViewController.view.layer setTransform:CATransform3DRotate(initialFromRotation, M_PI - h, 0.0, 1.0, 0.0)];
[toViewController.view.layer setTransform:CATransform3DRotate(initialToRotation, M_PI + h, 0.0, 1.0, 0.0)];
} completion:^(BOOL finished) {
BOOL wasCanceled = [transitionContext transitionWasCancelled];
if (wasCanceled) {
[transitionContext completeTransition:NO];
} else {
[transitionContext completeTransition:YES];
}
}];
The important thing is that both the front and back need to rotate together, and the front needs to be rotated in advance of the transition. You set up your view hierarchy in the container view but the transformation code is called on the viewControllers' views directly. Also, CoreAnimation doesn't seem to have any notion of clockwise or counter-clockwise, so you need to rotate either just over or just short of pi radians in order to ensure the right behavior.
Upvotes: 1
Reputation: 11845
I have a project that does this. Here is what we use for this project. It isn't CoreAnimation but gets the job done.
-(void)configureView{
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleSingleTap:)];
_contentsView = [[UIView alloc] initWithFrame:CGRectInset(self.view.bounds, 0, 0)];
_contentsView.backgroundColor = RGB(GRAY_BACKGROUND);
_contentsView.layer.cornerRadius = 0;
_contentsView.layer.masksToBounds = YES;
[_contentsView.layer setBorderColor: [RGB(DARK_GRAY) CGColor]];
[_contentsView.layer setBorderWidth: .5];
[_contentsView addGestureRecognizer:singleFingerTap];
[self.view addSubview:_contentsView];
CardFrontController *front = [CardFrontController new];
[front.view setFrame:CGRectMake(0, 0, 300, 300)];
[_contentsView addSubview:front.view];
_backView = [[UIView alloc] initWithFrame:CGRectInset(self.view.bounds, 0, 0)];
// load the back of the card
cardBack = [CardBackController new];
[_backView addSubview:cardBack.view];
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
// animate the card flipping
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
if (!_review) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
} else {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
}
[UIView commitAnimations];
// now stuff that we don't need to animate
if (!_review) {
[_contentsView removeFromSuperview];
[self.view addSubview:_backView];
}
else {
[_backView removeFromSuperview];
[self.view addSubview:_contentsView];
}
_review = !_review;
}
Upvotes: 0