2013年10月24日

[iOS]画面遷移のアニメーションをカスタムする

ベトナムで開発を始めてはや4ヶ月目、田中です。

iOS7から追加されたUIViewControllerAnimatedTransitioningとUIViewControllerTransitioningDelegateの2つのプロトコルを使用して画面遷移のアニメーションをカスタムする方法を紹介します。

transition1.jpg transition2.jpg transition3.jpg

このようにいつものスライドして遷移するのではなく、好きなアニメーションで画面を遷移させることができます。

ViewController.m
#import "ViewController.h"
#import "AnimationController.h"

@interface ViewController () <UIViewControllerTransitioningDelegate, UINavigationControllerDelegate>
{
    AnimationController *_animationController;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.navigationController.delegate = self;

    // アニメーションを管理するクラス
    _animationController =[[AnimationController alloc] init];
}

#pragma mark - UIViewControllerTransitioningDelegate

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return _animationController;
}

#pragma mark - UINavigationControllerDelegate

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    // 画面遷移の状態によってアニメーションの向きを変える
    _animationController.isReverse = operation == UINavigationControllerOperationPop;
    
    return _animationController;
}

@end

遷移元のクラスに以下を実装します。

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
ここにこれから作成するアニメーションクラスのインスタンスを返します。

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC;
画面が切り替わった時のnavigationControllerの動作が返ってくるので、状態に合わせてアニメーションクラスに値を設定しています。

次にアニメーションクラスを作成します。

AnimationController.h
#import 

@interface AnimationController : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic, assign) BOOL isReverse;
@property (nonatomic, assign) NSTimeInterval duration;

@end

AnimationController.m
#import "AnimationController.h"

@interface AnimationController() <UIViewControllerAnimatedTransitioning>

@end

@implementation AnimationController

- (id)init
{
    if (self = [super init])
    {
        // アニメーションの時間
        self.duration = 1.0f;
    }

    return self;
}

#pragma mark - UIViewControllerAnimatedTransitioning

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return self.duration;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    [self animateTransition:transitionContext fromView:fromVC.view toView:toVC.view];
}

#pragma mark - animation

// アニメーションを作る
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext fromView:(UIView *)fromView toView:(UIView *)toView
{
    UIView* containerView = [transitionContext containerView];
    [containerView addSubview:toView];
    
    if (!self.isReverse) [containerView sendSubviewToBack:toView];
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -0.002;
    [containerView.layer setSublayerTransform:transform];
    
    UIView *flippedSectionOfView = self.isReverse ? toView : fromView;
    
    if (self.isReverse) flippedSectionOfView.frame = CGRectMake(0, CGRectGetHeight(flippedSectionOfView.frame)*2, flippedSectionOfView.frame.size.height, flippedSectionOfView.frame.size.width);

    NSTimeInterval duration = [self transitionDuration:transitionContext];
    
    [UIView animateKeyframesWithDuration:duration
                                   delay:0.0
                                 options:0
                              animations:^{
                                  [UIView addKeyframeWithRelativeStartTime:0.0
                                                          relativeDuration:1.0
                                                                animations:^{
                                                                    flippedSectionOfView.layer.transform = [self rotate:self.isReverse];

                                                                    if (self.isReverse)
                                                                    {
                                                                        flippedSectionOfView.frame = CGRectMake(0, 0, CGRectGetWidth(containerView.frame), CGRectGetHeight(containerView.frame));
                                                                    } else
                                                                    {
                                                                        flippedSectionOfView.frame = CGRectMake(0, CGRectGetHeight(flippedSectionOfView.frame)*2, CGRectGetWidth(flippedSectionOfView.frame), CGRectGetHeight(flippedSectionOfView.frame));
                                                                    }
                                                                }];
                              } completion:^(BOOL finished) {
                                  if ([transitionContext transitionWasCancelled])
                                  {
                                      [toView removeFromSuperview];
                                  } else {
                                      [fromView removeFromSuperview];
                                  }

                                  [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                              }];
}

- (CATransform3D)rotate:(BOOL)initTransform
{
    CATransform3D transform = initTransform ? CATransform3DMakeRotation(0.0, 0.0, 0.0, 0.0) : CATransform3DMakeRotation(-M_PI_2, 0.0, 0.0, -2.0);

    return  transform;
}

@end
ここで、遷移前と遷移後のクラスのviewを使用して、どのようにアニメーションさせるかを実装していきます。
今回は遷移時に遷移前のviewが崩れ落ち、前の画面に戻ると遷移元のviewが崩れ落ちるアニメーションの逆再生で戻るように実装しました。
このように自分の好きなアニメーションが作成できますので、自分のアプリをよりユニークに作成することができます。

今回はボタンで画面を遷移しましたが、フリックに合わせてアニメーションしながら画面を遷移させることも可能ですので、また次回に記載していきたいと思います。



posted by Seesaa京都スタッフ at 02:34| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: