【iOS】画面遷移にUIKit Dynamicsのアニメーションを使ってみる
iOS7から使えるようになったカスタムの画面遷移UIViewControllerTransitioningDelegate
と
同じくiOS7から使えるようになった物理エンジンのラッパーUIKit Dynamics
を
組み合わせて画面遷移アニメーションを作ってみたいと思います。
今回は、重力によって地面に落ちて画面遷移するアニメーションを作成します。
落ちるときは、画面下に衝突した時に跳ねるようにしています。
また、表示するときは、天井にぶつかって跳ねる用にしました。
UIKit Dynamicsの準備
まずViewControllerの画面遷移で使用するUIViewControllerAnimatedTransitioning
に準拠したクラスを作成します。
今回は、UIKit Dynamics
を使ってアニメーションを行いたいので、UIDynamicBehavior
クラスを継承して作成します。
■ DropTransition.h
@interface INNDropViewBehavior : UIDynamicBehavior <UIViewControllerAnimatedTransitioning> @end
■ DropTransition.m
// class extension @interface DropTransition () <UIViewControllerAnimatedTransitioning, UIDynamicAnimatorDelegate> @property (nonatomic, strong) UIDynamicAnimator *animator; @property (nonatomic, strong) id <UIViewControllerContextTransitioning> transitionContext; @end @implementation DropTransition #pragma mark UIViewControllerAnimatedTransitioning - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { self.transitionContext = transitionContext; UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *containerView = [transitionContext containerView]; // アニメーション対象のView(=遷移先のView) UIView *frontView = nil; UIView *backView = nil; CGVector gravityDirection; if (self.isPresent) { // 表示 frontView = toVC.view; backView = fromVC.view; // 画面の表示と非表示で重力の方向を逆にする gravityDirection = CGVectorMake(0, -1.0); frontView.frame = CGRectOffset(frontView.frame, 0, frontView.bounds.size.height); } else { // 画面閉じる frontView = fromVC.view; backView = toVC.view; gravityDirection = CGVectorMake(0, 1.0); } /* Viewの準備 */ [containerView addSubview:backView]; // アニメーションを行うViewは、跳ね返りをするために縦方向に2倍の高さを取る CGRect frame = [transitionContext initialFrameForViewController:fromVC]; UIView *canvasView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height * 2)]; [canvasView addSubview:frontView]; [containerView addSubview:canvasView]; /* UIKitDynamicsの準備 */ self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:canvasView]; self.animator.delegate = self; // 重力 UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[frontView]]; [self addChildBehavior:gravityBehavior]; gravityBehavior.gravityDirection = gravityDirection; // 衝突 UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[frontView]]; collisionBehavior.translatesReferenceBoundsIntoBoundary = YES; [self addChildBehavior:collisionBehavior]; // property UIDynamicItemBehavior *propertyBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[frontView]]; propertyBehavior.elasticity = 0.4; // 弾力 propertyBehavior.friction = 1.0; // 摩擦 [self addChildBehavior:propertyBehavior]; [self.animator addBehavior:self]; } // TODO: 物理計算のアニメーションなので、秒数が正確にとれない.. - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return 0; } #pragma mark - UIViewControllerAnimatedTransitioning - (void)animationEnded:(BOOL)transitionCompleted { // contextの解放 [self.animator.referenceView removeFromSuperview]; self.animator = nil; self.transitionContext = nil; } #pragma mark - #pragma mark UIDynamicAnimatorDelegate - (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator { // アニメーションが終わった時点で通知する [self.transitionContext completeTransition:YES]; } @end
ViewControllerの準備
次に、画面遷移元となるViewControllerにUIViewControllerTransitioningDelegate
のデリゲートメソッドを準備します。
■ ViewController.m
// 遷移は、segueを使用 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"moveto"]) { // 遷移先のViewControllerのtransitionを独自で行うようにdelegateを指定する [segue.destinationViewController setTransitioningDelegate:self]; } } #pragma mark - UIViewControllerAnimatedTransitioning -(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { DropTransition *transition = [[DropTransition alloc] init]; transition = NO; return behavior; } - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { DropTransition *behavior = [[DropTransition alloc] init]; behavior.isPresent = YES; return transition; }
これだけで、モーダルの遷移を独自に実装できます。
面白いアニメーションも簡単に実装できそうなので色々試してみたいです。
※
画面の用途によってアニメーションを切り分けたい場合は、UIViewControllerAnimatedTransitioning
は遷移先のViewControllerに設定するでもいいかもしれません。