【OC】轉(zhuǎn)場(chǎng)動(dòng)畫(huà)-DIY_從零開(kāi)始

以下是iOS7之后,蘋(píng)果開(kāi)放轉(zhuǎn)場(chǎng)動(dòng)畫(huà)接口結(jié)構(gòu)圖,分別是UITabBarControllerDelegate 、UIViewControllerTransitiningDelegate、UINavigationControllerDelegate


第一節(jié):ViewController的模態(tài)跳轉(zhuǎn):動(dòng)畫(huà)自定義

UIViewControllerTransitioningDelegate
這個(gè)函數(shù)用來(lái)設(shè)置調(diào)用present方法和 dismiss 方法時(shí),進(jìn)行的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

/// 執(zhí)行present方法,進(jìn)行的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
/// presented:將要彈出的Controller
/// presenting:當(dāng)前的Controller
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

/// 執(zhí)行dismiss方法,進(jìn)行的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

/// 執(zhí)行present方法,進(jìn)行的交互式轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;

/// 執(zhí)行dismiss方法,進(jìn)行的交互式轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;

/// iOS8后提供的新接口  返回UIPresentationController處理轉(zhuǎn)場(chǎng)
- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source API_AVAILABLE(ios(8.0));

UIViewControllerAnimatedTransitioning 接口,是自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的重點(diǎn)

@protocol UIViewControllerAnimatedTransitioning <NSObject>
/// 返回動(dòng)畫(huà)執(zhí)行的時(shí)長(zhǎng)
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
/// 自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的實(shí)現(xiàn)
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@optional

UIViewControllerContextTransitioning 接口,轉(zhuǎn)場(chǎng)上下文,能獲取到轉(zhuǎn)場(chǎng)動(dòng)畫(huà)中參數(shù)及狀態(tài)

@protocol UIViewControllerContextTransitioning <NSObject>
/// 容器視圖: 用來(lái)表現(xiàn)動(dòng)畫(huà)
@property(nonatomic, readonly) UIView *containerView;
/// 是否應(yīng)該執(zhí)行動(dòng)畫(huà)
@property(nonatomic, readonly, getter=isAnimated) BOOL animated;
/// 是否可狡猾
@property(nonatomic, readonly, getter=isInteractive) BOOL interactive;
/// 是否被取消了
@property(nonatomic, readonly) BOOL transitionWasCancelled;
/// 轉(zhuǎn)場(chǎng)的風(fēng)格
@property(nonatomic, readonly) UIModalPresentationStyle presentationStyle;

/*
  可交互轉(zhuǎn)場(chǎng)動(dòng)畫(huà)特有的
*/
/// 更新轉(zhuǎn)場(chǎng)過(guò)程的百分比,用于可交互動(dòng)畫(huà)的閥值
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
/// 完成可交互的轉(zhuǎn)場(chǎng)交互動(dòng)作時(shí)調(diào)用
- (void)finishInteractiveTransition;
/// 取消可交互的轉(zhuǎn)場(chǎng)交互動(dòng)作時(shí)調(diào)用
- (void)cancelInteractiveTransition;

/// 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)被中斷、暫停時(shí)調(diào)用 
- (void)pauseInteractiveTransition API_AVAILABLE(ios(10.0));
/// 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)完成時(shí)調(diào)用
- (void)completeTransition:(BOOL)didComplete;


/*
  獲取轉(zhuǎn)場(chǎng)中兩個(gè)視圖控制器
  UITransitionContextViewControllerKey 的定義
  UITransitionContextFromViewControllerKey /// 原視圖控制器
  UITransitionContextToViewControllerKey  /// 跳轉(zhuǎn)的視圖控制器
*/
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;


/*
  直接獲取轉(zhuǎn)場(chǎng)中的視圖
  UITransitionContextViewKey 的定義
  UITransitionContextFromViewKey  /// 原控制器的視圖
  UITransitionContextToViewKey    /// 轉(zhuǎn)場(chǎng)控制器的視圖
*/
- (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;
/// 獲取視圖控制器轉(zhuǎn)場(chǎng)后的位置
- (CGRect)finalFrameForViewController:(UIViewController *)vc;
@end

小案例:自定義一個(gè)從右邊滑入的present轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

第一步:自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà),創(chuàng)建PanPresentAnimation實(shí)現(xiàn)UIViewControllerAnimatedTransitioning接口

@interface PanPresentAnimation : NSObject<UIViewControllerAnimatedTransitioning>
@end


@implementation PanPresentAnimation
/// 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫(huà)時(shí)間
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.8f;
}

/// 自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    /// 獲取切入ViewController
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    /// 設(shè)置要切入的ViewController的初始位置
    /// 實(shí)現(xiàn)的效果:從右邊滑入的效果
    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];
    
    /// 自動(dòng)定義動(dòng)畫(huà):彈出動(dòng)畫(huà)
    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協(xié)議,返回自定義present轉(zhuǎn)場(chǎng)動(dòng)畫(huà)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轉(zhuǎn)場(chǎng)動(dòng)畫(huà)對(duì)象:BouncePresentAnimation
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return [PanPresentAnimation new];
}

@end
最終效果

可交互轉(zhuǎn)場(chǎng)動(dòng)畫(huà)自定義

可交互轉(zhuǎn)場(chǎng)動(dòng)畫(huà),按我的理解就是可以控制轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的進(jìn)度,比如蘋(píng)果系統(tǒng)提供的右滑返回上一級(jí)一樣。
在UIViewControllerTransitioningDelegate協(xié)議中有兩個(gè)可以定義可交互轉(zhuǎn)場(chǎng)的方法:interactionControllerForPresentation方法很少用,因?yàn)橄乱患?jí)界面是不確定的,常用的是interactionControllerForDismissal,因?yàn)樯弦患?jí)界面是確定的。

- (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;

對(duì)于UIViewControllerTransitioningDelegate協(xié)議中對(duì)于dismiss轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的方法有以下兩個(gè):
一個(gè)是不可交互的普通轉(zhuǎn)場(chǎng):animationControllerForDismissedController
一個(gè)是可交互的轉(zhuǎn)場(chǎng):interactionControllerForDismissal
兩者的關(guān)系蘋(píng)果官方是這么寫(xiě)的:

(1)interactionControllerForDismissal方法中的anmatore參數(shù)是由animationControllerForDismissedController方法返回的
(2)如果interactionControllerForDismissal 返回結(jié)果是nil,則是不執(zhí)行可交互動(dòng)畫(huà),執(zhí)行普通的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(即animationControllerForDismissedController返回定義的動(dòng)畫(huà))
(3)如果要執(zhí)行可交互動(dòng)畫(huà),那么也必須要實(shí)現(xiàn)animationControllerForDismissedController方法,并返回一個(gè)普通的轉(zhuǎn)場(chǎng)動(dòng)畫(huà),如果animationControllerForDismissedController返回是nil或沒(méi)實(shí)現(xiàn),那么interactionControllerForDismissal方法是不會(huì)執(zhí)行的

小案例:在上一個(gè)案例中繼續(xù)開(kāi)發(fā),在presentB界面后,自定義dismiss可交換的轉(zhuǎn)場(chǎng)動(dòng)畫(huà),效果:右滑返回。(案例:A present B,B dismiss A)

第一步:創(chuàng)建自定義可交換動(dòng)畫(huà),PanInteractiveTransition實(shí)現(xiàn)UIViewControllerInteractiveTransitioning接口

@interface PanInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
/// 是否處于交互的狀態(tài)
@property (nonatomic, assign) BOOL interacting;

/// 用于為B界面添加拖動(dòng)手勢(shì)
/// @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界面添加拖動(dòng)手勢(shì)
- (void)forPresentingViewController:(UIViewController *)viewController {
    self.presentingViewController = viewController;
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesgture:)];
    [viewController.view addGestureRecognizer: pan];
}

/// 拖動(dòng)手勢(shì)響應(yīng)
- (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) {
        /// 手勢(shì)開(kāi)始
        case UIGestureRecognizerStateBegan:
            /// 標(biāo)記:交互中
            self.interacting = YES;
            /// 執(zhí)行dismiss方法
            [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
            break;
        /// 拖動(dòng)中
        case UIGestureRecognizerStateChanged: {
            /// 更新轉(zhuǎn)場(chǎng)動(dòng)畫(huà)進(jìn)度
            [self updateAniProgess:persent];
            break;
        }
        /// 拖動(dòng)結(jié)束
        case UIGestureRecognizerStateEnded:
        /// 拖動(dòng)取消、中斷
        case UIGestureRecognizerStateCancelled: {
            /// 標(biāo)記:交互結(jié)束
            self.interacting = NO;
            /// 如果滑動(dòng)查過(guò)50%,則返回,否則取消返回原點(diǎn)
            if (persent > 0.5) {
                [self finish];
            }else{
                [self cancel];
            }
            break;
        }
        default:
            break;
    }
}

/// UIViewControllerInteractiveTransitioning協(xié)議方法
/// 觸發(fā):開(kāi)始交互轉(zhuǎn)場(chǎng)時(shí)
/// 作用:將兩個(gè)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];
}


/// 更新動(dòng)畫(huà)狀態(tài)
- (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;
}

/// 轉(zhuǎn)場(chǎng)結(jié)束
- (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];
    }];
}

/// 轉(zhuǎn)場(chǎng)取消
- (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

第二步:實(shí)現(xiàn)interactionControllerForDismissal,返回自定義的可交互動(dòng)畫(huà)

#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轉(zhuǎn)場(chǎng)動(dòng)畫(huà)  
    /// 這里個(gè)類實(shí)現(xiàn)則寫(xiě)了,與present動(dòng)畫(huà)方式是一樣的,詳情請(qǐng)看最后的demo代碼
    return self.presentAnimation;
}

-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    /// 普通的dimiss轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
    return [PanDimissAnimation new];
}

-(id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
    /// 這里要判斷是否交互觸發(fā)的dismiss方法,如果不是,則返回nil,表示執(zhí)行普通dimiss轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
    return self.panInteractiveTransition.interacting ? self.panInteractiveTransition : nil;
}
@end

補(bǔ)充:

可交互轉(zhuǎn)場(chǎng)接口UIViewControllerInteractiveTransitioning,系統(tǒng)還提供了”繼承于“ UIViewControllerInteractiveTransitioning的接口UIPercentDrivenInteractiveTransition。
UIPercentDrivenInteractiveTransition 做的事跟我們上面自定義的PanInteractiveTransition差不多,主要包括updateInteractiveTransition:cancelInteractiveTransitionfinishInteractiveTransition,有些屬性是iOS10之后才引入的,為低版本兼容,就不采用。比如記錄是否處于可交互狀態(tài)wantsInteractiveStart,而是使用自定義的屬性。

但是,但是,但是,UIPercentDrivenInteractiveTransitionUIViewControllerInteractiveTransitioning還是很不一樣的,官方文檔是這樣描述的:


大體的意思是:
(1)UIPercentDrivenInteractiveTransition對(duì)象依賴于UIViewControllerTransitioningDelegate協(xié)議返回的動(dòng)畫(huà)對(duì)象去執(zhí)行設(shè)置動(dòng)畫(huà)和執(zhí)行動(dòng)畫(huà)的(也就是說(shuō):UIViewControllerTransitioningDelegate百分比可交互轉(zhuǎn)場(chǎng)動(dòng)畫(huà)UIPercentDrivenInteractiveTransition設(shè)置,是在協(xié)議方法中animationControllerForPresentedController:animationControllerForDismissedController:返回對(duì)象中設(shè)置的,還有一個(gè)要注意的地方,請(qǐng)看備注1
(2)可交互轉(zhuǎn)場(chǎng)進(jìn)度,用戶可以使用updateInteractiveTransition:finishInteractiveTransition、cancelInteractiveTransition三個(gè)方法控制

備注1:
animationControllerForDismissedController:方法為例,執(zhí)行completeTransition:方法時(shí)就不能寫(xiě)死YES,需要設(shè)置成[transitionContext completeTransition:![transitionContext transitionWasCancelled]];因?yàn)榻换マD(zhuǎn)場(chǎng)可能取消了,那么就需要返回初始位置

案例:重寫(xiě)上例dismiss可交互返回的動(dòng)畫(huà)為例

@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];
    /// 跳轉(zhuǎn)VC
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
    /// 獲取初始位置
    CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];
    /// 計(jì)算轉(zhuǎn)場(chǎng)最后的位置
    CGRect finalFrame = CGRectOffset(initialFrame, screenW, 0);
    
    /// 都需要將toVC.view添加到視圖容器containerView中,不論dismiss動(dòng)畫(huà)還是present動(dòng)畫(huà)
    /// 將toVC.view 添加到視圖容器containerView中
    /// 如果不添加也可以,不過(guò)效果不好,在切換的時(shí)候,會(huì)有一小段是白屏
    /// 推薦添加,并且要置于底部,否則會(huì)覆蓋在fromVC.view 上面
    [transitionContext.containerView addSubview:toVC.view];
    [transitionContext.containerView sendSubviewToBack:toVC.view];
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
        /// 設(shè)置最后的位置
        fromVC.view.frame = finalFrame;
    } completion:^(BOOL finished) {
        /// 動(dòng)畫(huà)完成
        /// (1)completeTransition調(diào)用如果返回YES,則會(huì)將fromVC.view 從ContainView中移除
        /// 而這里是否完成通過(guò)![transitionContext transitionWasCancelled]賦值,
        /// 因?yàn)閷?duì)于可交互動(dòng)畫(huà),是有可能被取消的,那么就需要把fromVC.view復(fù)原原來(lái)的位置,不能移除
        /// (當(dāng)可交互動(dòng)畫(huà)是繼承于UIPercentDrivenInteractiveTransition就會(huì)出現(xiàn)這種情況)
        /// (2)如果是沒(méi)有可交互的動(dòng)畫(huà),那么直接返回YES也是可以,但是推薦使用第一種方式
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}
@end
@interface PanPercentIinteractiveTransition : UIPercentDrivenInteractiveTransition
/// 是否處于交互的狀態(tài)
@property (nonatomic, assign) BOOL interacting;
/// 用于為B界面添加拖動(dòng)手勢(shì)
/// @param viewController BController
- (void)forPresentingViewController:(UIViewController *)viewController;
@end


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

@implementation PanPercentIinteractiveTransition

/// 用于為B界面添加拖動(dòng)手勢(shì)
/// @param viewController BController
- (void)forPresentingViewController:(UIViewController *)viewController {
    self.presentingViewController = viewController;
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesgture:)];
    [viewController.view addGestureRecognizer: pan];
}

/// 拖動(dòng)手勢(shì)響應(yīng)
- (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) {
        /// 手勢(shì)開(kāi)始
        case UIGestureRecognizerStateBegan: {
            /// 標(biāo)記:交互中
            self.interacting = YES;
            /// 執(zhí)行dismiss方法
            [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
        }
            break;
        /// 拖動(dòng)中
        case UIGestureRecognizerStateChanged: {
            NSLog(@"update=====");
            /// 更新轉(zhuǎn)場(chǎng)動(dòng)畫(huà)進(jìn)度
            [self updateInteractiveTransition:persent];
        }
            break;
        /// 拖動(dòng)結(jié)束
        case UIGestureRecognizerStateEnded:
        /// 拖動(dòng)取消、中斷
        case UIGestureRecognizerStateCancelled: {
            /// 標(biāo)記:交互結(jié)束
            self.interacting = NO;
            /// 如果滑動(dòng)查過(guò)50%,則返回,否則取消返回原點(diǎn)
            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


第二節(jié):導(dǎo)航欄轉(zhuǎn)場(chǎng)動(dòng)畫(huà)自定義

由第一節(jié)已經(jīng)知道自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的流程及設(shè)計(jì)的類、接口等,其實(shí)導(dǎo)航欄轉(zhuǎn)場(chǎng)與模態(tài)轉(zhuǎn)場(chǎng)自定義也是一樣的基本原理,不同的是變成設(shè)置UINavigationController實(shí)例的delegate(UINavigationControllerDelegate),其余的都是一樣,這里就不展開(kāi)細(xì)講???♂?。

/// 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫(huà),如果返回nil,則使用系統(tǒng)默認(rèn)的導(dǎo)航欄轉(zhuǎn)場(chǎng)動(dòng)畫(huà),不論P(yáng)USH或POP
/// UINavigationControllerOperation 枚舉
/// UINavigationControllerOperationNone, //無(wú)
/// UINavigationControllerOperationPush, //push操作
/// UINavigationControllerOperationPop,  //pop操作
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC  API_AVAILABLE(ios(7.0));

/// 設(shè)置可交互動(dòng)畫(huà)
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController API_AVAILABLE(ios(7.0));

第三節(jié):tabBarController切換轉(zhuǎn)場(chǎng)動(dòng)畫(huà)自定義

UITabBarController也是自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的,套路一樣,只是delegate不同(UITabBarControllerDelegate)

/// 普通轉(zhuǎn)場(chǎng)
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC  API_AVAILABLE(ios(7.0));

/// 可交互轉(zhuǎn)場(chǎng)
- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                      interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController API_AVAILABLE(ios(7.0));

解惑:ContainerView

相信你在看文章的時(shí)候,一定有個(gè)疑惑,到底containerView是什么鬼?是臨時(shí)的視圖容器么?它跟FromVC.view 與 ToVC.view 之間的視圖層級(jí)關(guān)系到底是怎么樣?(這很重要,很關(guān)鍵,對(duì)于自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)有很大的幫助)
筆者剛開(kāi)始的時(shí)候也是有很大的疑惑,接下來(lái)以 A PUSH B為例,Demo代碼下載地
鏈接: https://pan.baidu.com/s/1Usk2KtjoLu2PenGusVZ3ig 密碼: tceb


從上圖可以看到containView是一直存在的(UINavigationController、UITabBarController、UIWindow都是帶有一個(gè)轉(zhuǎn)場(chǎng)視圖UITransitionView,用于展示轉(zhuǎn)場(chǎng)動(dòng)畫(huà)及控制器視圖的),所以上文自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)中提到一定要將toVIewController.view 添加到ContainerView中。


參考文章

WWDC 2013 Session筆記 - iOS7中的ViewController切換
iOS自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
【實(shí)戰(zhàn)】快速集成自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)&手勢(shì)驅(qū)動(dòng)
iOS 自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)淺談
iOS自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(push、pop動(dòng)畫(huà))
iOS 兩行代碼實(shí)現(xiàn)自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
使用 UIPercentDrivenInteractiveTransition

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容