天天品尝iOS7甜点 Day14:Interactive View Controller Transitions

为模态视图控制器添加交互式视图转换

参考

这篇文章可能会用到之前两篇文章的知识:

Flip Transition Animation - 翻转过渡效果

默认的模态视图翻转效果

自定义模态视图翻转效果

实现方式:

  1. 视图层次结构

  2. 自定义渐变动画类,遵守 UIViewControllerAnimatedTransitioning 协议以实现具体的动画效果:

    // ********************************************
    // SCFlipAnimation.h
    #import <Foundation/Foundation.h>
    
    /**
     遵守<UIViewControllerAnimatedTransitioning>协议的自定义渐变动画
     */
    @interface SCFlipAnimation : NSObject <UIViewControllerAnimatedTransitioning>
    
    /** 确定页面的翻转方向 */
    @property (nonatomic, assign) BOOL dismissal;
    
    @end
    
    // ********************************************
    // SCFlipAnimation.m
    #import "SCFlipAnimation.h"
    
    @implementation SCFlipAnimation
    
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
         {
        // 获取各自的视图控制器
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        
        // 获取视图
        UIView *containerView = [transitionContext containerView];
        UIView *fromView = fromVC.view;
        UIView *toView = toVC.view;
        
        // 添加 toView 到容器视图
        [containerView addSubview:toView];
        
        // 设置 frames
        CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];
        fromView.frame = initialFrame;
        toView.frame = initialFrame;
        
        // 构建3D转换需要的视角
        CATransform3D transform = CATransform3DIdentity;
        transform.m34 = -1/CGRectGetHeight(initialFrame);
        containerView.layer.sublayerTransform = transform;
        
        // 翻转方向
        CGFloat direction = self.dismissal ? -1.0 : 1.0;
        
        // toView 反向翻转 pi/2
        toView.layer.transform = CATransform3DMakeRotation(-direction * M_PI_2, 1, 0, 0);
        
        // 关键帧动画
        [UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext]
                                       delay:0.0
                                     options:0
                                  animations:^{
                                      // First half is rotating in
                [UIView addKeyframeWithRelativeStartTime:0.0
                                        relativeDuration:0.5
                                              animations:^{
            // fromView 正向翻转 pi/2
            fromView.layer.transform = CATransform3DMakeRotation(direction * M_PI_2, 1, 0, 0);
                                              }];
                [UIView addKeyframeWithRelativeStartTime:0.5
                                        relativeDuration:0.5
                                              animations:^{
            // toView 还原
            toView.layer.transform = CATransform3DMakeRotation(0, 1, 0, 0);
                                              }];
                                  } completion:^(BOOL finished) {
                                      [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                                  }];
    }
    
    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
         {
        return 1.0;
    }
    
    @end
    
  3. First View Controller,遵守 UIViewControllerTransitioningDelegate 协议

    @interface SCViewController () <UIViewControllerTransitioningDelegate> {
        SCFlipAnimation *_flipAnimation;
    }
    
    @end
    
    @implementation SCViewController
    
    #pragma mark - Lifecycle
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
     // Do any additional setup after loading the view, typically from a nib.
        _flipAnimation = [SCFlipAnimation new];
    }
    
    #pragma mark - 
    
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if([segue.destinationViewController isKindOfClass:[SCModalViewController class]]) {
            SCModalViewController *vc = (SCModalViewController *)segue.destinationViewController;
        }
    }
    
    #pragma mark - UIViewControllerTransitioningDelegate
    
    // 弹出模态视图控制器
    // 返回遵守 UIViewControllerAnimatedTransitioning 协议的实例
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        _flipAnimation.dismissal = NO;
        return _flipAnimation;
    }
    
    // 消失模态视图控制器
    // 返回遵守 UIViewControllerAnimatedTransitioning 协议的实例
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
    {
        _flipAnimation.dismissal = YES;
        return _flipAnimation;
    }
    
  4. Model View Controller 实现按钮方法

    // 使模态视图控制器消失
    - (IBAction)handleDismissPressed:(id)sender {
        [self dismissViewControllerAnimated:YES completion:NULL];
    }
    

Interactive transitioning - 交互式转换

用手势上滑、下滑操作实现视图转换

  1. 自定义 UIPercentDrivenInteractiveTransition 子类对象,用于手势处理

    // ********************************************
    // SCFlipAnimationInteractor.h
    #import <UIKit/UIKit.h>
    #import "SCInteractiveTransitionViewControllerDelegate.h"
    
    @interface SCFlipAnimationInteractor : UIPercentDrivenInteractiveTransition
    
    @property (nonatomic, strong, readonly) UIPanGestureRecognizer *gestureRecogniser;
    @property (nonatomic, assign, readonly) BOOL interactionInProgress;
    
    // 实现 SCInteractiveTransitionViewControllerDelegate 自定义交互式转场协议的任意视图控制器
    @property (nonatomic, weak) UIViewController<SCInteractiveTransitionViewControllerDelegate> *presentingVC;
    
    @end
    
    // ********************************************
    // SCFlipAnimationInteractor.m
    #import "SCFlipAnimationInteractor.h"
    
    @interface SCFlipAnimationInteractor ()
    
    @property (nonatomic, strong, readwrite) UIPanGestureRecognizer *gestureRecogniser;
    @property (nonatomic, assign, readwrite) BOOL interactionInProgress;
    
    @end
    
    @implementation SCFlipAnimationInteractor
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.gestureRecogniser = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
        }
        return self;
    }
    
    #pragma mark - Gesture recognition
    // 手势操作的方法
    - (void)handlePan:(UIPanGestureRecognizer *)pgr
    {
        CGPoint translation = [pgr translationInView:pgr.view];
        // 手势百分比,用百分比的方式来激活指定的动画过程。
        CGFloat percentage  = fabs(translation.y / CGRectGetHeight(pgr.view.bounds));
        switch (pgr.state) {
            case UIGestureRecognizerStateBegan: {
                // 手势开始,执行翻转动画
                self.interactionInProgress = YES;
                // 向目标对象传达执行动画的命令
                [self.presentingVC proceedToNextViewController];
                break;
            }
    
            case UIGestureRecognizerStateChanged: {
                // 手势进行中,更新翻转进度
                [self updateInteractiveTransition:percentage];
                break;
            }
                
            case UIGestureRecognizerStateEnded: {
                // 手势结束,分进度值处理
                if(percentage < 0.5) {
                    // 1.进度小于0.5,取消转场动画
                    [self cancelInteractiveTransition];
                } else {
                    // 2.进度大于0.5,自动完成转场动画
                    [self finishInteractiveTransition];
                }
                self.interactionInProgress = NO;
                break;
            }
                
            case UIGestureRecognizerStateCancelled: {
                // 取消手势
                [self cancelInteractiveTransition];
                self.interactionInProgress = NO;
                break;
            }
                
            default:
                break;
        }
    }
    
    @end
    
  2. 自定义交互协议

#import <Foundation/Foundation.h>

// 1.该协议的作用:
// 将手势与目标视图相关联,手势开始时,自定义的手势处理类需要向目标视图发送执行视图转场的命令。
// 2.为什么要将手势与目标视图关联呢?
// 因为手势是添加到 window 上的,这样就可以响应预期的行为。
// 3.为什么手势要添加到 window 上而不是视图上?
// 因为动画发生的时候,视图控制器中的视图将会被移除,因此手势就没有办法响应行为了。
@protocol SCInteractiveTransitionViewControllerDelegate <NSObject>

- (void)proceedToNextViewController;

@end

  1. First View Controller

    // ********************************************
    // SCViewController.h
    #import <UIKit/UIKit.h>
    
    @interface SCViewController : UIViewController
    
    @end
    
    // ********************************************
    // SCViewController.m
    #import "SCViewController.h"
    #import "SCModalViewController.h"
    #import "SCFlipAnimationInteractor.h"
    #import "SCFlipAnimation.h"
    #import "SCInteractiveTransitionViewControllerDelegate.h"
    
    @interface SCViewController () <SCInteractiveTransitionViewControllerDelegate, UIViewControllerTransitioningDelegate> {
        SCFlipAnimationInteractor *_animationInteractor;
        SCFlipAnimation *_flipAnimation;
    }
    
    @end
    
    @implementation SCViewController
    
    #pragma mark - Lifecycle
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
     // Do any additional setup after loading the view, typically from a nib.
        _animationInteractor = [SCFlipAnimationInteractor new];
        _flipAnimation = [SCFlipAnimation new];
    }
    
    - (void)viewDidAppear:(BOOL)animated
    {
        // 添加手势识别器到window对象中。
        // 这是因为动画发生的时候,视图控制器中的视图将会被移除,因此手势就没有办法响应行为了。
        // 把它添加到window上面,将会确保我们期待的行为。
        if (![self.view.window.gestureRecognizers containsObject:_animationInteractor.gestureRecogniser]) {
            [self.view.window addGestureRecognizer:_animationInteractor.gestureRecogniser];
        }
        // 在第一页中,交互的接收者指向 self 自身
        _animationInteractor.presentingVC = self;
    }
    
    #pragma mark - 
    
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if([segue.destinationViewController isKindOfClass:[SCModalViewController class]]) {
            // Set the delegate
            SCModalViewController *vc = (SCModalViewController *)segue.destinationViewController;
            vc.transitioningDelegate = self;
            // 模态视图的手势交互器也指向这个类的手势交互器(弱引用)
            // 也就是说,这两个视图的手势交互器指向的是同一个对象
            vc.interactor = _animationInteractor;
        }
    }
    
    #pragma mark - SCInteractiveTransitionViewControllerDelegate
    
    - (void)proceedToNextViewController
    {
        [self performSegueWithIdentifier:@"displayModal" sender:self];
    }
    
    #pragma mark - UIViewControllerTransitioningDelegate
    
    // 弹出模态视图控制器
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        _flipAnimation.dismissal = NO;
        return _flipAnimation;
    }
    
    // 消失模态视图控制器
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
    {
        _flipAnimation.dismissal = YES;
        return _flipAnimation;
    }
    
    // 交互式转场方法1:展示
    // 返回一个实现 UIViewControllerInteractiveTransitioning 协议的对象
    - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator
    {
        return _animationInteractor.interactionInProgress ? _animationInteractor : nil;
    }
    
    // 交互式转场方法2:消失
    // 返回一个实现 UIViewControllerInteractiveTransitioning 协议的对象
    - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
    {
        return _animationInteractor.interactionInProgress ? _animationInteractor : nil;
    }
    
    @end
    
  2. Model View Controller

    // ********************************************
    // SCModalViewController.h
    #import <UIKit/UIKit.h>
    #import "SCInteractiveTransitionViewControllerDelegate.h"
    #import "SCFlipAnimationInteractor.h"
    
    @interface SCModalViewController : UIViewController <SCInteractiveTransitionViewControllerDelegate>
    
    - (IBAction)handleDismissPressed:(id)sender;
    
    @property (nonatomic, weak) SCFlipAnimationInteractor *interactor;
    
    @end
    
    // ********************************************
    // SCModalViewController.m
    #import "SCModalViewController.h"
    
    @implementation SCModalViewController
    
    #pragma mark - Lifecycle
    
    - (void)viewDidAppear:(BOOL)animated
    {
        // 重新设置哪一个视图控制器是交互式转场的接收者
        // 在模态页面中,交互的接收者又重新设置为模态页面自身
        self.interactor.presentingVC = self;
    }
    
    #pragma mark - IBActions
    
    // 使模态视图控制器消失
    - (IBAction)handleDismissPressed:(id)sender {
        [self dismissViewControllerAnimated:YES completion:NULL];
    }
    
    #pragma mark - SCInteractiveTransitionViewControllerDelegate
    
    - (void)proceedToNextViewController
    {
        [self dismissViewControllerAnimated:YES completion:NULL];
    }
    
    @end
    

The End.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容