iOS-自定义转场

阅读及实践笔记


相关api的记录及介绍

<strong>喵神文章传送门</strong>

/*这个接口用来提供切换上下文给开发者使用,包含了从哪个VC到哪个VC等各类信息*/
@protocol UIViewControllerContextTransitioning

-(UIView *)containerView; VC切换所发生的view容器,开发者应该将切出的view移除,将切入的view加入到该view容器中。
-(UIViewController *)viewControllerForKey:(NSString *)key; 提供一个key,返回对应的VC。现在的SDK中key的选择只有UITransitionContextFromViewControllerKey和UITransitionContextToViewControllerKey两种,分别表示将要切出和切入的VC。
-(CGRect)initialFrameForViewController:(UIViewController *)vc; 某个VC的初始位置,可以用来做动画的计算。
-(CGRect)finalFrameForViewController:(UIViewController *)vc; 与上面的方法对应,得到切换结束时某个VC应在的frame。
-(void)completeTransition:(BOOL)didComplete; 向这个context报告切换已经完成。


/* 自定义转场动画中使用 */
/*这个接口负责切换的具体内容,也即“切换中应该发生什么”。开发者在做自定义切换效果时大部分代码会是用来实现这个接口*/
@protocol UIViewControllerAnimatedTransitioning

-(NSTimeInterval)transitionDuration:(id < UIViewControllerContextTransitioning >)transitionContext; 系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间

-(void)animateTransition:(id < UIViewControllerContextTransitioning >)transitionContext; 在进行切换的时候将调用该方法,我们对于切换时的UIView的设置和动画都在这个方法中完成。


/* ViewController中调用 */
@protocol UIViewControllerTransitioningDelegate

// 动画
-(id< UIViewControllerAnimatedTransitioning >)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
-(id< UIViewControllerAnimatedTransitioning >)animationControllerForDismissedController:(UIViewController *)dismissed;

// 交互
-(id< UIViewControllerInteractiveTransitioning >)interactionControllerForPresentation:(id < UIViewControllerAnimatedTransitioning >)animator;
-(id< UIViewControllerInteractiveTransitioning >)interactionControllerForDismissal:(id < UIViewControllerAnimatedTransitioning >)animator;


/* UIViewControllerInteractiveTransitioning 提供百分比控制交互切换*/

-(Float)completionSpeed 返回速度  1 - self.percentComplete;

-(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
-(void)cancelInteractiveTransition 报告交互取消,返回切换前的状态
–(void)finishInteractiveTransition 报告交互完成,更新到切换后的状态

  • 注意点
    locationInView:获取到的是手指点击屏幕实时的坐标点;
    translationInView:获取到的是手指移动后,在相对坐标中的偏移量

Modal

<strong>注意:当我们在被modal出的控制器中,向self发送dismissViewController的方法时,这个消息,会被直接转发到显示它的VC中去</strong>

Present

<strong>BouncePresentTransition的具体实现</strong>

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface SPBouncePresentTransition : NSObject<UIViewControllerAnimatedTransitioning>

@end

#import "SPBouncePresentTransition.h"

@implementation SPBouncePresentTransition

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{

    return 0.8f;
    
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{

    // 获取相关ViewController
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    CGRect bounds = [UIScreen mainScreen].bounds;
    // 设置被modal出界面view的frame
    CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
    // 设置目标界面frame的offset,即初始位置
    toVC.view.frame = CGRectOffset(finalFrame, 0, bounds.size.height);
    
    // 取出containerView,将目标vc的view进行添加
    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    
    // 设置view的frame动画效果
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
        toVC.view.frame = finalFrame;
    } completion:^(BOOL finished) {
        // 提交transion完成
        [transitionContext completeTransition:YES];
    }];
}

@end

在viewcontroller中如何使用

1.遵守UIViewControllerTransitioningDelegate协议
2.设置modal的vc的transitioningDelegate属性
3.实现代理方法

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

具体可参照如下代码

#pragma mark - Transition
- (void)testForModalTransition{

    SPModalViewController *vc = [[SPModalViewController alloc] init];
    vc.transitioningDelegate = self;// 设置代理
    vc.dismissAction = ^{
        [self dismissViewControllerAnimated:YES completion:nil];
    };
    [self presentViewController:vc animated:YES completion:nil];
    
}

// 代理方法
// init
/* self.bouncePresent = [[SPBouncePresentTransition alloc]init]; */
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{

    return self.bouncePresent;
    
}

效果如下

animateTransition.gif
Dismiss

<strong>滑动效果的转场实现</strong>
需要注意的是,务必同时实现两个代理方法
触发顺序,为动画类->交互类,系统会优先处理animationControllerForDismissedController方法,如果有,则dismiss操作均为自定义dismissTransition(demo中的transitionDuration已经设置为1.5s,效果可如gif动图所示),如果没有设置,那么则为系统的dismiss动画。

// 动画类
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
// 交互类
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator

具体可参照如下代码

1.NormalDismiss

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface SPNormalDismissTransition : NSObject<UIViewControllerAnimatedTransitioning>

@end
#import "SPNormalDismissTransition.h"

@implementation SPNormalDismissTransition

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{

    return 1.5f;
    
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{

    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];
    CGRect finalFrame = CGRectOffset(initialFrame, 0, [UIScreen mainScreen].bounds.size.height);
    
    UIView *containerView  = [transitionContext containerView];// 承载当前活动vc的view的containerView
    [containerView addSubview:toVC.view];
    [containerView sendSubviewToBack:toVC.view];// 将视图置于下层
    
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromVC.view.frame = finalFrame;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
    
}

@end

2.SwipeTransition

#import <UIKit/UIKit.h>

@interface SPPercentSwipeTransition : UIPercentDrivenInteractiveTransition

@property (nonatomic, assign) BOOL interacting;// 用来判断是否处理交互
- (void)addSwipeTransition2viewController:(UIViewController *)viewController;// 传入要添加panRecognizer的viewController

@end
#import "SPPercentSwipeTransition.h"

@interface SPPercentSwipeTransition()

@property (nonatomic, weak) UIViewController *presenedController;// 当前被modal出的控制器 注意weak的使用,强引用造成无法释放
@property (nonatomic, assign) BOOL canEndTransition;// 当超过屏幕高度的1/4时,我们认为交互可以结束

@end

@implementation SPPercentSwipeTransition

- (void)addSwipeTransition2viewController:(UIViewController *)viewController{

    self.presenedController = viewController;
    [self addRecognizer2View:viewController.view];
    
}

- (void)addRecognizer2View:(UIView *)view{

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
    [view addGestureRecognizer:pan];
    [pan addTarget:self action:@selector(handleRecognizer:)];
    
}

- (void)handleRecognizer:(UIPanGestureRecognizer *)recognizer{

    CGRect screenBounds = [UIScreen mainScreen].bounds;
    CGPoint point = [recognizer translationInView:recognizer.view.superview];// translationInView
    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
            self.interacting = YES;
            [self.presentingController dismissViewControllerAnimated:YES completion:nil];
            break;
        case UIGestureRecognizerStateChanged:
        {
            CGFloat actionPoint = point.y/(screenBounds.size.height /2);// 有效距离为屏幕高度的一半
            actionPoint = fminf(fmaxf(actionPoint, 0.0), 1.0);
            self.canEndTransition = actionPoint > 0.5;// 滑动超过屏幕高度的1/4则可完成转场
            [self updateInteractiveTransition:actionPoint];
        }
            break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        {
            self.interacting = NO;
            if (self.canEndTransition)
            {
                [self finishInteractiveTransition];
            }
            else if (!self.canEndTransition || recognizer.state == UIGestureRecognizerStateCancelled)
            {
                [self cancelInteractiveTransition];
            }
        }
            break;
        default:
            break;
    }
    
}

- (CGFloat)completionSpeed{// 返回转场的速度

    return 1 - self.percentComplete;
    
}

效果如下

swipeTransition.gif
SemiModal

实现一个常见的semi半挂式modal,将我们上述的AnimationTransition(present)、NormalDismiss部分稍作处理即可
效果如下:

semiModal.gif

首先分析实现思路:
1.semi部分,在bouncePresentTransition中的toVC的view进行圆角处理,同时设置它在containerView中的frame为目标样式
2.背景部分,将当前fromVC的view进行截图,添加到转场容器containerView中,然后改变它的transform中的scale属性,进行等比例缩放,同时将底层viewController的view进行隐藏处理
3.因为上文中,我们已经处理了基于UIPercentDrivenInteractiveTransitionswipeTransition,所以不需要再关心百分比变化
4.上文中处理的normalDismiss,只是简单的对视图进行了切换,因为考虑到semi的样式会对底层viewController的view进行隐藏处理,所以我们需要特别注意,在transition完成时,要将目标vc的view显示出来

思路有了,那么接下来就是实现,具体请看代码:

<strong>截图方法 </strong>

- (UIImage *)getSnapShotFromView:(UIView *)view{
    
    CGRect rect = view.bounds;
    UIGraphicsBeginImageContextWithOptions(rect.size, false, 0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *snapShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return snapShot;
    
}

<strong>改造的bouncePresent</strong>

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{

    // 获取相关ViewController
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    CGRect bounds = [UIScreen mainScreen].bounds;
    // 设置被modal出界面view的frame
//    CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
    
    // -- semi效果 --
    CGRect finalFrame = CGRectMake(0, bounds.size.height/3, bounds.size.width, bounds.size.height);
    
    // 设置目标界面frame的offset,即初始位置
    toVC.view.frame = CGRectOffset(finalFrame, 0, bounds.size.height);
    
    // -- semi效果 --
    toVC.view.layer.cornerRadius = 15;
    [toVC.view.layer masksToBounds];
    
    UIImageView *snapView = [[UIImageView alloc] initWithImage:[self getSnapShotFromView:fromVC.view]];
    snapView.frame = fromVC.view.bounds;
    snapView.tag = 404;//标记一下
    
    // 将fromvc的view隐藏掉
    fromVC.view.hidden = YES;
    
    // 取出containerView(视图管理容器),将目标vc的view进行添加
    UIView *containerView = [transitionContext containerView];
    // -- semi效果 --
    [containerView addSubview:snapView];
    // 注意添加顺序
    [containerView addSubview:toVC.view];
    
    // 设置view的frame动画效果
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
        
        toVC.view.frame = finalFrame;
        snapView.transform = CGAffineTransformMakeScale(0.85, 0.85);
    
    } completion:^(BOOL finished) {
        
        // 提交transion完成
        if ([transitionContext transitionWasCancelled]) {
            [transitionContext completeTransition:NO];
            [snapView removeFromSuperview];
            fromVC.view.hidden = NO; // 注意隐藏操作
        }else{
            [transitionContext completeTransition:YES];
        }
        
    }];
}

<strong>改造的normalDismiss</strong>

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{

    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];
    CGRect finalFrame = CGRectOffset(initialFrame, 0, [UIScreen mainScreen].bounds.size.height);
    
    
    UIView *containerView  = [transitionContext containerView];// 承载当transitionview的containerView
    UIView *snapView = nil;// 取出截图 tag = 404
    for (UIView *sub in containerView.subviews) {
        if (sub.tag == 404) {
            snapView = sub;
            break;
        }
    }
    [containerView addSubview:toVC.view];
    [containerView sendSubviewToBack:toVC.view];// 将视图置于下层
    
    
    
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        
        fromVC.view.frame = finalFrame;
        snapView.transform = CGAffineTransformIdentity;
        
    } completion:^(BOOL finished) {
        
        BOOL transitionComplete = ![transitionContext transitionWasCancelled];
        [transitionContext completeTransition:transitionComplete];
        
        if (transitionComplete) {
            toVC.view.hidden = NO;
            [snapView removeFromSuperview];
        }else{
            toVC.view.hidden = YES;
        }
        
        
    }];
    
}

Push&Pop

Push

<strong>在viewcontroller中如何使用</strong>

1.遵守UINavigationControllerDelegate协议
2.设置viewcontroller.navigationController.delegate的代理
3.实现代理方法

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC

这里需要特别注意一点,对于Push和Pop,都会在此方法中返回,需要我们对operation进行类型判断,返回正确类型的自定义transition
同时要考虑一种特殊情况,就是自己并非第一层viewController时,从自身pop出去的情况

if (fromVC == self && operation == UINavigationControllerOperationPop)
 {// 从自己pop出去
     return nil;
 }

(我们可以发现,Push同上文中提到的Modal的处理方式一致,只是遵守的协议不同)

先看效果

transPush.gif

分析一下思路:
1.将点击的Cell中的imageView,传入到转场容器container中,转换其坐标、截图并添加(注意同toVC.view的添加顺序),我们命名为snapView
2.获取fromVC中的目标图片位置,设置为snapview的终点frame

大概看一下我们如何实现,写一下几个比较关键的地方

// 1.fromVC,处理点击CollectionView的点击事件
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{

    PushCell *cell = (PushCell *)[collectionView cellForItemAtIndexPath:indexPath];
    self.transImgView = cell.transImg;
    
    SPGakkiViewController *gakki = [[SPGakkiViewController alloc] init];
    [self.navigationController pushViewController:gakki animated:YES];
    
}

// 2.transition中的写法
SPGakkiViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    SPPushViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    // 获取视图容器
    UIView *containerView = [transitionContext containerView];
    
    // 获取需要变换的视图
    UIImageView *transImgView = fromVC.transImgView;
    UIView *snapView = [transImgView snapshotViewAfterScreenUpdates:NO];
    snapView.frame = [transImgView convertRect:transImgView.bounds toView:containerView];
    
    // 先隐藏视图
    toVC.showContent = NO;

    [containerView addSubview:toVC.view];
    [containerView addSubview:snapView];
    
    // 动画部分
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:1/0.6 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        
        snapView.frame = toVC.targetFrame;
        
    } completion:^(BOOL finished) {
        
        snapView.hidden = YES;
        toVC.showContent = YES;
        [snapView removeFromSuperview];
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        
    }];

Pop

效果如下

transPop.gif

如果一路写到这里,想必已经明白转场动画的基本使用,所以我们不再赘述,只附效果的实现代码

SPPushViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
     SPGakkiViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    // 获取视图容器
    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    toVC.view.alpha = 0;
   
    // 截图
    UIView *snapView = [fromVC.imageView snapshotViewAfterScreenUpdates:NO];

    // 隐藏
    fromVC.showContent = NO;
    
    // 目标位置
    CGRect startFrame = fromVC.targetFrame;
    CGRect targetFrame = [toVC.transImgView convertRect:toVC.transImgView.bounds toView:containerView];
    snapView.frame = startFrame;
    [containerView addSubview:snapView];
    
    // 开始动画
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:1/0.6 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        
        snapView.frame = targetFrame;
        snapView.alpha = 0;
        toVC.view.alpha = 1;
        
    } completion:^(BOOL finished) {
        
        snapView.hidden = YES;
        [snapView removeFromSuperview];
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        
    }];

** 注意!注意!注意! [transitionContext completeTransition:![transitionContext transitionWasCancelled]];务必在动画结束时上报转场结束状态(此处手动鲜血红) **

** 返回手势 **
只需要我们在之前写的percentSwipeTransition中稍加改造即可,我们将它稍加封装,代码如下

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger,SPTransitionType) {

    SPTransitionTypePush,
    SPTransitionTypeModal
    
};

@interface SPPercentSwipeTransition : UIPercentDrivenInteractiveTransition

@property (nonatomic, assign) BOOL interacting;

- (void)addSwipeTransition2viewController:(UIViewController *)viewController withType:(SPTransitionType)type;

@end
#import "SPPercentSwipeTransition.h"

@interface SPPercentSwipeTransition()

@property (nonatomic, weak) UIViewController *presentingController;
@property (nonatomic, assign) BOOL canEndTransition;
@property (nonatomic, assign) SPTransitionType currentType;

@end

@implementation SPPercentSwipeTransition

- (void)addSwipeTransition2viewController:(UIViewController *)viewController withType:(SPTransitionType)type{

    self.currentType = type;
    [self addSwipeTransition2viewController:viewController];
    
}

- (void)addSwipeTransition2viewController:(UIViewController *)viewController{

    self.presentingController = viewController;
    [self addRecognizer2View:viewController.view];
    
}

- (void)addRecognizer2View:(UIView *)view{

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
    [view addGestureRecognizer:pan];
    [pan addTarget:self action:@selector(handleRecognizer:)];
    
}

- (void)handleRecognizer:(UIPanGestureRecognizer *)recognizer{

    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
            self.interacting = YES;
            [self moveBackWithTransitionType:_currentType];
            break;
        case UIGestureRecognizerStateChanged:
            [self handleTransitionWithType:_currentType andRecognizer:recognizer];
            break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        {
            self.interacting = NO;
            if (self.canEndTransition)
            {
                [self finishInteractiveTransition];
            }
            else if (!self.canEndTransition || recognizer.state == UIGestureRecognizerStateCancelled)
            {
                [self cancelInteractiveTransition];
            }
        }
            break;
        default:
            break;
    }
    
}

- (void)moveBackWithTransitionType:(SPTransitionType)type{

    switch (type) {
        case SPTransitionTypeModal:
            [self.presentingController dismissViewControllerAnimated:YES completion:nil];
            break;
        case SPTransitionTypePush:
            [self.presentingController.navigationController popViewControllerAnimated:YES];
            break;
    }
    
}

- (void)handleTransitionWithType:(SPTransitionType)type andRecognizer:(UIPanGestureRecognizer *)recognizer{

    CGRect screenBounds = [UIScreen mainScreen].bounds;
    CGPoint point = [recognizer translationInView:recognizer.view.superview];// translationInView
    
    switch (type) {
        case SPTransitionTypePush:
        {
            CGFloat percent = point.x/(screenBounds.size.width /2);// 有效距离为屏幕宽度的一半
            percent = fminf(fmax(percent, 0.0), 1.0);
            self.canEndTransition = percent > 0.5;// 活动超过屏幕宽度的1/4则可完成转场
            [self updateInteractiveTransition:percent];
        }
            break;
        case SPTransitionTypeModal:
        {
            CGFloat actionPoint = point.y/(screenBounds.size.height /2);// 有效距离为屏幕高度的一半
            actionPoint = fminf(fmaxf(actionPoint, 0.0), 1.0);
            self.canEndTransition = actionPoint > 0.5;// 滑动超过屏幕高度的1/4则可完成转场
            [self updateInteractiveTransition:actionPoint];
        }
            break;
    }
    
}

- (CGFloat)completionSpeed{

    return 1 - self.percentComplete;
    
}

@end

因为加入了手势处理,所以我们需要将Pop中上报转场完成的地方稍加改动
考虑手势取消转场的情况,取消时,将fromVC的内容再次显示即可

// 开始动画
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:1/0.6 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        
        snapView.frame = targetFrame;
        snapView.alpha = 0;
        toVC.view.alpha = 1;
        
    } completion:^(BOOL finished) {
        
        BOOL completeTransiton = ![transitionContext transitionWasCancelled];
        snapView.hidden = YES;
        
        if (!completeTransiton) fromVC.showContent = YES;
        
        [snapView removeFromSuperview];
        [transitionContext completeTransition:completeTransiton];
        
    }];

看一下我们的最终效果

swipeTrans.gif

总结

** 1.理解ContainerView的作用 **
** 2.务必上报transition结束或取消状态 **
** 3.区分UIViewControllerTransitioningDelegateUINavigationControllerDelegate的使用场景**
** 4.转场动画的关键,在于处理fromVC和toVC的view的变化,最终还是回归到UIView动画 **
** 5.体会自定义transition时,代理方法带来的简洁高效 **

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

推荐阅读更多精彩内容