【OC】转场动画-DIY_从零开始

以下是iOS7之后,苹果开放转场动画接口结构图,分别是UITabBarControllerDelegate 、UIViewControllerTransitiningDelegate、UINavigationControllerDelegate


第一节:ViewController的模态跳转:动画自定义

UIViewControllerTransitioningDelegate
这个函数用来设置调用present方法和 dismiss 方法时,进行的转场动画

/// 执行present方法,进行的转场动画
/// presented:将要弹出的Controller
/// presenting:当前的Controller
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

/// 执行dismiss方法,进行的转场动画
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

/// 执行present方法,进行的交互式转场动画
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;

/// 执行dismiss方法,进行的交互式转场动画
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;

/// iOS8后提供的新接口  返回UIPresentationController处理转场
- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source API_AVAILABLE(ios(8.0));

UIViewControllerAnimatedTransitioning 接口,是自定义转场动画的重点

@protocol UIViewControllerAnimatedTransitioning <NSObject>
/// 返回动画执行的时长
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
/// 自定义转场动画的实现
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@optional

UIViewControllerContextTransitioning 接口,转场上下文,能获取到转场动画中参数及状态

@protocol UIViewControllerContextTransitioning <NSObject>
/// 容器视图: 用来表现动画
@property(nonatomic, readonly) UIView *containerView;
/// 是否应该执行动画
@property(nonatomic, readonly, getter=isAnimated) BOOL animated;
/// 是否可狡猾
@property(nonatomic, readonly, getter=isInteractive) BOOL interactive;
/// 是否被取消了
@property(nonatomic, readonly) BOOL transitionWasCancelled;
/// 转场的风格
@property(nonatomic, readonly) UIModalPresentationStyle presentationStyle;

/*
  可交互转场动画特有的
*/
/// 更新转场过程的百分比,用于可交互动画的阀值
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
/// 完成可交互的转场交互动作时调用
- (void)finishInteractiveTransition;
/// 取消可交互的转场交互动作时调用
- (void)cancelInteractiveTransition;

/// 转场动画被中断、暂停时调用 
- (void)pauseInteractiveTransition API_AVAILABLE(ios(10.0));
/// 转场动画完成时调用
- (void)completeTransition:(BOOL)didComplete;


/*
  获取转场中两个视图控制器
  UITransitionContextViewControllerKey 的定义
  UITransitionContextFromViewControllerKey /// 原视图控制器
  UITransitionContextToViewControllerKey  /// 跳转的视图控制器
*/
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;


/*
  直接获取转场中的视图
  UITransitionContextViewKey 的定义
  UITransitionContextFromViewKey  /// 原控制器的视图
  UITransitionContextToViewKey    /// 转场控制器的视图
*/
- (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key API_AVAILABLE(ios(8.0));

@property(nonatomic, readonly) CGAffineTransform targetTransform API_AVAILABLE(ios(8.0));

/// 获取视图控制器的初始位置
- (CGRect)initialFrameForViewController:(UIViewController *)vc;
/// 获取视图控制器转场后的位置
- (CGRect)finalFrameForViewController:(UIViewController *)vc;
@end

小案例:自定义一个从右边滑入的present转场动画

第一步:自定义转场动画,创建PanPresentAnimation实现UIViewControllerAnimatedTransitioning接口

@interface PanPresentAnimation : NSObject<UIViewControllerAnimatedTransitioning>
@end


@implementation PanPresentAnimation
/// 设置转场动画时间
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.8f;
}

/// 自定义转场动画
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    /// 获取切入ViewController
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    /// 设置要切入的ViewController的初始位置
    /// 实现的效果:从右边滑入的效果
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
    toVC.view.frame = CGRectOffset(finalFrame, screenBounds.size.width, 0);
    
    /// 将切入的ViewController的View添加到containerView
    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    
    /// 自动定义动画:弹出动画
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    [UIView animateWithDuration:duration
                          delay:0.0
         usingSpringWithDamping:0.8
          initialSpringVelocity:5
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         toVC.view.frame = finalFrame;
                     } completion:^(BOOL finished) {
                         /// 必须要告诉context 切换完成
                         [transitionContext completeTransition:YES];
                     }];
}
@end

第二步:遵守UIViewControllerTransitioningDelegate协议,返回自定义present转场动画PanPresentAnimation

#import "AViewController.h"
#import "BViewController.h"
#import "PanPresentAnimation"

@interface MainViewController ()<UIViewControllerTransitioningDelegate>
@end

@implementation MainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [button setTitle:@"Click me" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

-(void) buttonClicked:(id)sender {
    BViewController *mvc =  [[BViewController alloc] init];
    mvc.transitioningDelegate = self;
    mvc.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:mvc animated:YES completion:nil];
}

/// 返回自定义的present转场动画对象:BouncePresentAnimation
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return [PanPresentAnimation new];
}

@end
最终效果

可交互转场动画自定义

可交互转场动画,按我的理解就是可以控制转场动画的进度,比如苹果系统提供的右滑返回上一级一样。
在UIViewControllerTransitioningDelegate协议中有两个可以定义可交互转场的方法:interactionControllerForPresentation方法很少用,因为下一级界面是不确定的,常用的是interactionControllerForDismissal,因为上一级界面是确定的。

- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;

- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;

对于UIViewControllerTransitioningDelegate协议中对于dismiss转场动画的方法有以下两个:
一个是不可交互的普通转场:animationControllerForDismissedController
一个是可交互的转场:interactionControllerForDismissal
两者的关系苹果官方是这么写的:

(1)interactionControllerForDismissal方法中的anmatore参数是由animationControllerForDismissedController方法返回的
(2)如果interactionControllerForDismissal 返回结果是nil,则是不执行可交互动画执行普通的转场动画(即animationControllerForDismissedController返回定义的动画)
(3)如果要执行可交互动画,那么也必须要实现animationControllerForDismissedController方法,并返回一个普通的转场动画,如果animationControllerForDismissedController返回是nil或没实现,那么interactionControllerForDismissal方法是不会执行的

小案例:在上一个案例中继续开发,在presentB界面后,自定义dismiss可交换的转场动画,效果:右滑返回。(案例:A present B,B dismiss A)

第一步:创建自定义可交换动画,PanInteractiveTransition实现UIViewControllerInteractiveTransitioning接口

@interface PanInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
/// 是否处于交互的状态
@property (nonatomic, assign) BOOL interacting;

/// 用于为B界面添加拖动手势
/// @param viewController BController
- (void)forPresentingViewController:(UIViewController *)viewController;
@end
@interface PanInteractiveTransition()
@property (nonatomic, strong) id<UIViewControllerContextTransitioning> context;
@property (nonatomic, strong) UIViewController *presentingViewController;
@end

@implementation PanInteractiveTransition

/// 为B界面添加拖动手势
- (void)forPresentingViewController:(UIViewController *)viewController {
    self.presentingViewController = viewController;
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesgture:)];
    [viewController.view addGestureRecognizer: pan];
}

/// 拖动手势响应
- (void)handleGesgture:(UIPanGestureRecognizer *)pan {
    CGPoint translation = [pan translationInView:pan.view.superview];
    CGFloat persent = translation.x / [UIScreen mainScreen].bounds.size.width;
    if (persent < 0) {
        return;
    }
    persent = fabs(persent);
    switch (pan.state) {
        /// 手势开始
        case UIGestureRecognizerStateBegan:
            /// 标记:交互中
            self.interacting = YES;
            /// 执行dismiss方法
            [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
            break;
        /// 拖动中
        case UIGestureRecognizerStateChanged: {
            /// 更新转场动画进度
            [self updateAniProgess:persent];
            break;
        }
        /// 拖动结束
        case UIGestureRecognizerStateEnded:
        /// 拖动取消、中断
        case UIGestureRecognizerStateCancelled: {
            /// 标记:交互结束
            self.interacting = NO;
            /// 如果滑动查过50%,则返回,否则取消返回原点
            if (persent > 0.5) {
                [self finish];
            }else{
                [self cancel];
            }
            break;
        }
        default:
            break;
    }
}

/// UIViewControllerInteractiveTransitioning协议方法
/// 触发:开始交互转场时
/// 作用:将两个ViewController中View的添加到视图容器containerView中
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    self.context = transitionContext;
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    [[transitionContext containerView] insertSubview:toVC.view belowSubview:fromVC.view];
}


/// 更新动画状态
- (void)updateAniProgess:(CGFloat)progress {
    UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
    CGRect finalRect = CGRectMake([UIScreen mainScreen].bounds.size.width * progress, 0, frameVC.bounds.size.width, frameVC.bounds.size.height);
    frameVC.frame = finalRect;
}

/// 转场结束
- (void)finish {
    [UIView animateWithDuration:0.2 animations:^{
        UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
        CGRect finalRect = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, frameVC.bounds.size.width, frameVC.bounds.size.height);
        frameVC.frame = finalRect;
    } completion:^(BOOL finished) {
        [self.context completeTransition:YES];
    }];
}

/// 转场取消
- (void)cancel {
    [UIView animateWithDuration:0.2 animations:^{
        UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey];
        CGRect finalRect = CGRectMake(0, 0, frameVC.bounds.size.width, frameVC.bounds.size.height);
        frameVC.frame = finalRect;
    } completion:^(BOOL finished) {
        [self.context cancelInteractiveTransition];
    }];
}

@end

第二步:实现interactionControllerForDismissal,返回自定义的可交互动画

#import "AController.h"
#import "BController.h"
#import "PanPresentAnimation.h"
#import "PanDimissAnimation.h"
#import "PanInteractiveTransition.h"

@interface AController ()<UIViewControllerTransitioningDelegate>
@property (nonatomic, strong) PanPresentAnimation *presentAnimation;
@property (nonatomic, strong) PanInteractiveTransition *panInteractiveTransition;
@end

@implementation AController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        _presentAnimation = [PanPresentAnimation new];
        _panInteractiveTransition = [PanInteractiveTransition new];
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [button setTitle:@"Click me" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}


-(void) buttonClicked:(id)sender {
    BController *mvc =  [[BController alloc] init];
    mvc.transitioningDelegate = self;
    mvc.modalPresentationStyle = UIModalPresentationFullScreen;
    [self.panInteractiveTransition forPresentingViewController:mvc];
    [self presentViewController:mvc animated:YES completion:nil];
}


- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    /// 普通的present转场动画  
    /// 这里个类实现则写了,与present动画方式是一样的,详情请看最后的demo代码
    return self.presentAnimation;
}

-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    /// 普通的dimiss转场动画
    return [PanDimissAnimation new];
}

-(id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
    /// 这里要判断是否交互触发的dismiss方法,如果不是,则返回nil,表示执行普通dimiss转场动画
    return self.panInteractiveTransition.interacting ? self.panInteractiveTransition : nil;
}
@end

补充:

可交互转场接口UIViewControllerInteractiveTransitioning,系统还提供了”继承于“ UIViewControllerInteractiveTransitioning的接口UIPercentDrivenInteractiveTransition
UIPercentDrivenInteractiveTransition 做的事跟我们上面自定义的PanInteractiveTransition差不多,主要包括updateInteractiveTransition:cancelInteractiveTransitionfinishInteractiveTransition,有些属性是iOS10之后才引入的,为低版本兼容,就不采用。比如记录是否处于可交互状态wantsInteractiveStart,而是使用自定义的属性。

但是,但是,但是UIPercentDrivenInteractiveTransitionUIViewControllerInteractiveTransitioning还是很不一样的,官方文档是这样描述的:


大体的意思是:
(1)UIPercentDrivenInteractiveTransition对象依赖于UIViewControllerTransitioningDelegate协议返回的动画对象去执行设置动画和执行动画的(也就是说:UIViewControllerTransitioningDelegate百分比可交互转场动画UIPercentDrivenInteractiveTransition设置,是在协议方法中animationControllerForPresentedController:animationControllerForDismissedController:返回对象中设置的,还有一个要注意的地方,请看备注1
(2)可交互转场进度,用户可以使用updateInteractiveTransition:finishInteractiveTransitioncancelInteractiveTransition三个方法控制

备注1:
animationControllerForDismissedController:方法为例,执行completeTransition:方法时就不能写死YES,需要设置成[transitionContext completeTransition:![transitionContext transitionWasCancelled]];因为交互转场可能取消了,那么就需要返回初始位置

案例:重写上例dismiss可交互返回的动画为例

@interface PanDimissAnimation : NSObject<UIViewControllerAnimatedTransitioning>
@end

@implementation PanDimissAnimation

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.4f;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    NSLog(@"animateTransition: ===== %d",transitionContext.isInteractive);
    /// 原VC
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    /// 跳转VC
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
    /// 获取初始位置
    CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];
    /// 计算转场最后的位置
    CGRect finalFrame = CGRectOffset(initialFrame, screenW, 0);
    
    /// 都需要将toVC.view添加到视图容器containerView中,不论dismiss动画还是present动画
    /// 将toVC.view 添加到视图容器containerView中
    /// 如果不添加也可以,不过效果不好,在切换的时候,会有一小段是白屏
    /// 推荐添加,并且要置于底部,否则会覆盖在fromVC.view 上面
    [transitionContext.containerView addSubview:toVC.view];
    [transitionContext.containerView sendSubviewToBack:toVC.view];
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
        /// 设置最后的位置
        fromVC.view.frame = finalFrame;
    } completion:^(BOOL finished) {
        /// 动画完成
        /// (1)completeTransition调用如果返回YES,则会将fromVC.view 从ContainView中移除
        /// 而这里是否完成通过![transitionContext transitionWasCancelled]赋值,
        /// 因为对于可交互动画,是有可能被取消的,那么就需要把fromVC.view复原原来的位置,不能移除
        /// (当可交互动画是继承于UIPercentDrivenInteractiveTransition就会出现这种情况)
        /// (2)如果是没有可交互的动画,那么直接返回YES也是可以,但是推荐使用第一种方式
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}
@end
@interface PanPercentIinteractiveTransition : UIPercentDrivenInteractiveTransition
/// 是否处于交互的状态
@property (nonatomic, assign) BOOL interacting;
/// 用于为B界面添加拖动手势
/// @param viewController BController
- (void)forPresentingViewController:(UIViewController *)viewController;
@end


@interface PanPercentIinteractiveTransition()
@property (nonatomic, strong) UIViewController *presentingViewController;
@end

@implementation PanPercentIinteractiveTransition

/// 用于为B界面添加拖动手势
/// @param viewController BController
- (void)forPresentingViewController:(UIViewController *)viewController {
    self.presentingViewController = viewController;
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesgture:)];
    [viewController.view addGestureRecognizer: pan];
}

/// 拖动手势响应
- (void)handleGesgture:(UIPanGestureRecognizer *)pan {
    CGPoint translation = [pan translationInView:pan.view.superview];
    CGFloat persent = translation.x / [UIScreen mainScreen].bounds.size.width;
    if (persent < 0) {
        return;
    }
    persent = fabs(persent);
    NSLog(@"========== persent:%f",persent);
    switch (pan.state) {
        /// 手势开始
        case UIGestureRecognizerStateBegan: {
            /// 标记:交互中
            self.interacting = YES;
            /// 执行dismiss方法
            [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
        }
            break;
        /// 拖动中
        case UIGestureRecognizerStateChanged: {
            NSLog(@"update=====");
            /// 更新转场动画进度
            [self updateInteractiveTransition:persent];
        }
            break;
        /// 拖动结束
        case UIGestureRecognizerStateEnded:
        /// 拖动取消、中断
        case UIGestureRecognizerStateCancelled: {
            /// 标记:交互结束
            self.interacting = NO;
            /// 如果滑动查过50%,则返回,否则取消返回原点
            if (persent > 0.5) {
                [self finishInteractiveTransition];
            }else{
                [self cancelInteractiveTransition];
            }
        }
            break;
        default:
            break;
    }
}

@end

DEMO

Present/Dimiss 代码
链接: https://pan.baidu.com/s/1hkR_mhB2AsOq9hu82n-nOQ 密码: 612k


第二节:导航栏转场动画自定义

由第一节已经知道自定义转场动画的流程及设计的类、接口等,其实导航栏转场与模态转场自定义也是一样的基本原理,不同的是变成设置UINavigationController实例的delegate(UINavigationControllerDelegate),其余的都是一样,这里就不展开细讲🙅‍♂️。

/// 设置转场动画,如果返回nil,则使用系统默认的导航栏转场动画,不论PUSH或POP
/// UINavigationControllerOperation 枚举
/// UINavigationControllerOperationNone, //无
/// UINavigationControllerOperationPush, //push操作
/// UINavigationControllerOperationPop,  //pop操作
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC  API_AVAILABLE(ios(7.0));

/// 设置可交互动画
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController API_AVAILABLE(ios(7.0));

第三节:tabBarController切换转场动画自定义

UITabBarController也是自定义转场动画的,套路一样,只是delegate不同(UITabBarControllerDelegate)

/// 普通转场
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC  API_AVAILABLE(ios(7.0));

/// 可交互转场
- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                      interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController API_AVAILABLE(ios(7.0));

解惑:ContainerView

相信你在看文章的时候,一定有个疑惑,到底containerView是什么鬼?是临时的视图容器么?它跟FromVC.view 与 ToVC.view 之间的视图层级关系到底是怎么样?(这很重要,很关键,对于自定义转场动画有很大的帮助)
笔者刚开始的时候也是有很大的疑惑,接下来以 A PUSH B为例,Demo代码下载地
链接: https://pan.baidu.com/s/1Usk2KtjoLu2PenGusVZ3ig 密码: tceb


从上图可以看到containView是一直存在的(UINavigationController、UITabBarController、UIWindow都是带有一个转场视图UITransitionView,用于展示转场动画及控制器视图的),所以上文自定义转场动画中提到一定要将toVIewController.view 添加到ContainerView中。


参考文章

WWDC 2013 Session笔记 - iOS7中的ViewController切换
iOS自定义转场动画
【实战】快速集成自定义转场动画&手势驱动
iOS 自定义转场动画浅谈
iOS自定义转场动画(push、pop动画)
iOS 两行代码实现自定义转场动画
使用 UIPercentDrivenInteractiveTransition

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

推荐阅读更多精彩内容