当前位置: 首页 > 编程笔记 >

iOS开发中的ViewController转场切换效果实现简介

鲜于子琪
2023-03-14
本文向大家介绍iOS开发中的ViewController转场切换效果实现简介,包括了iOS开发中的ViewController转场切换效果实现简介的使用技巧和注意事项,需要的朋友参考一下

在iOS7之前,View Controller的切换主要有4种:

  1. Push/Pop,NavigationViewController
  2. Present and dismis Modal
  3. UITabBarController
  4. addChildViewController(一般用于自定义的继承于 UIViewController 的容器子类)

iOS5,调用- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(5_0);

(1)前面3种方法这里就不多说了,很常见的系统方法.至于第四种,我在前面文章-剖析网易标签栏的效果中已经做了阐述,但是它提供的容器转场动画只可以实现一些简单的UIView动画,但是难以重用,耦合高.
(2)关键的API:
A.动画控制器 (Animation Controllers) 遵从 UIViewControllerAnimatedTransitioning 协议,并且负责实际执行动画。
B.交互控制器 (Interaction Controllers) 通过遵从 UIViewControllerInteractiveTransitioning 协议来控制可交互式的转场。
C.转场代理 (Transitioning Delegates) 根据不同的转场类型方便的提供需要的动画控制器和交互控制器。
   其中UINavigationControllerDelegate delegate 中新增了2个方法给NavigationController
               UIViewControllerTransitioningDelegate 新增transitioningDelegate  给Modal的Present和Dismis


  UITabBarControllerDelegate delegate

- (id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController

interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController NS_AVAILABLE_IOS(7_0);

- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController

animationControllerForTransitionFromViewController:(UIViewController *)fromVC

toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0); 

D.转场上下文 (Transitioning Contexts) 定义了转场时需要的元数据,比如在转场过程中所参与的视图控制器和视图的相关属性。 转场上下文对象遵从 UIViewControllerContextTransitioning 协议,并且这是由系统负责生成和提供的。
E.转场协调器(Transition Coordinators) 可以在运行转场动画时,并行的运行其他动画。 转场协调器遵从 UIViewControllerTransitionCoordinator 协议。
(3)新的API主要提供了2种VC切换的方式:
A.非交互式切换,即定义一种从一个VC到另一个VC的动画效果,切换的时候自动播放,
B.交互式切换,这种方式同样需要定义动画效果,只是这个动画效果会根据跟随交互式手势来切换VC并同时播放动画效果。iOS7提供了一个默认的基于百分比的动画实现 UIPercentDrivenInteractiveTransition,而且根据WWDC的说明,最简单的实现交互式动画的方法就是通过继承 UIPercentDrivenInteractiveTransition。

苹果给我们开发者提供的是都是协议接口,所以我们能够很好的单独提出来写成一个个类,在里面实现我们各种自定义效果.
(4)来看看实现UIViewControllerAnimatedTransitioning的自定义动画类

/** 

 *  自定义的动画类 

 *  实现协议------>@protocol UIViewControllerAnimatedTransitioning 

 *  这个接口负责切换的具体内容,也即“切换中应该发生什么” 

 */  

@interface MTHCustomAnimator : NSObject <UIViewControllerAnimatedTransitioning>  

  

@end  

  

@implementation MTHCustomAnimator  

  

// 系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间  

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

{  

    return 1.0;  

}  

  

// 完成容器转场动画的主要方法,我们对于切换时的UIView的设置和动画都在这个方法中完成  

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

{  

    // 可以看做为destination ViewController  

    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  

    // 可以看做为source ViewController  

    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];  

    // 添加toView到容器上  

        // 如果是XCode6 就可以用这段  

    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)  

    {  

        // iOS8 SDK 新API  

        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];  

        //UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];  

        [[transitionContext containerView] addSubview:toView];  

    }else{  

        // 添加toView到容器上  

        [[transitionContext containerView] addSubview:toViewController.view];  

    }  

      

    // 如果是XCode5 就是用这段  

    [[transitionContext containerView] addSubview:toViewController.view];  

    toViewController.view.alpha = 0.0;  

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{  

        // 动画效果有很多,这里就展示个左偏移  

        fromViewController.view.transform = CGAffineTransformMakeTranslation(-320, 0);  

        toViewController.view.alpha = 1.0;  

    } completion:^(BOOL finished) {  

        fromViewController.view.transform = CGAffineTransformIdentity;  

        // 声明过渡结束-->记住,一定别忘了在过渡结束时调用 completeTransition: 这个方法  

        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];  

    }];  

}  

       PS:从协议中两个方法可以看出,上面两个必须实现的方法需要一个转场上下文参数,这是一个遵从UIViewControllerContextTransitioning 协议的对象。通常情况下,当我们使用系统的类时,系统框架为我们提供的转场代理(Transitioning Delegates),为我们创建了转场上下文对象,并把它传递给动画控制器。
 
      

// MainViewController  

@interface MTHMainViewController () <UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>  

  

@property (nonatomic,strong) MTHCustomAnimator *customAnimator;  

@property (nonatomic,strong) PDTransitionAnimator *minToMaxAnimator;  

@property (nonatomic,strong) MTHNextViewController *nextVC;  

// 交互控制器 (Interaction Controllers) 通过遵从 UIViewControllerInteractiveTransitioning 协议来控制可交互式的转场。  

@property (strong, nonatomic) UIPercentDrivenInteractiveTransition* interactionController;  

@end  

  

@implementation MTHMainViewController  

  

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil  

{  

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];  

    if (self) {  

        // Custom initialization  

    }  

    return self;  

}  

  

- (void)viewDidLoad  

{  

    [super viewDidLoad];  

    // Do any additional setup after loading the view.  

    self.navigationItem.title = @"Demo";  

    self.view.backgroundColor = [UIColor yellowColor];  

    // 设置代理  

    self.navigationController.delegate = self;  

    // 设置转场动画  

    self.customAnimator = [[MTHCustomAnimator alloc] init];  

    self.minToMaxAnimator = [PDTransitionAnimator new];  

  

    self.nextVC = [[MTHNextViewController alloc] init];  

    // Present的代理和自定义设置  

    _nextVC.transitioningDelegate = self;  

    _nextVC.modalPresentationStyle = UIModalPresentationCustom; (貌似有BUG)换成modalTransitionStyle = UIModalPresentationCustom  

      

    // Push  

    UIButton *pushButton = [UIButton buttonWithType:UIButtonTypeSystem];  

    pushButton.frame = CGRectMake(140, 200, 40, 40);  

    [pushButton setTitle:@"Push" forState:UIControlStateNormal];  

    [pushButton addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside];  

    [self.view addSubview:pushButton];  

      

    // Present  

    UIButton *modalButton = [UIButton buttonWithType:UIButtonTypeSystem];  

    modalButton.frame = CGRectMake(265, 500, 50, 50);  

    [modalButton setTitle:@"Modal" forState:UIControlStateNormal];  

    [modalButton addTarget:self action:@selector(modal) forControlEvents:UIControlEventTouchUpInside];  

    [self.view addSubview:modalButton];  

      

    // 实现交互操作的手势  

    UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didClickPanGestureRecognizer:)];  

    [self.navigationController.view addGestureRecognizer:panRecognizer];  

}  

  

  

- (void)push  

{  

    [self.navigationController pushViewController:_nextVC animated:YES];  

}  

  

- (void)modal  

{  

    [self presentViewController:_nextVC animated:YES completion:nil];  

}  

  

#pragma mark - UINavigationControllerDelegate iOS7新增的2个方法  

// 动画特效  

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

{  

    /** 

     *  typedef NS_ENUM(NSInteger, UINavigationControllerOperation) { 

     *     UINavigationControllerOperationNone, 

     *     UINavigationControllerOperationPush, 

     *     UINavigationControllerOperationPop, 

     *  }; 

     */  

    if (operation == UINavigationControllerOperationPush) {  

        return self.customAnimator;  

    }else{  

        return nil;  

    }  

}  

  

// 交互  

- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController                           interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController  

{  

    /** 

     *  在非交互式动画效果中,该方法返回 nil 

     *  交互式转场,自我理解意思是,用户能通过自己的动作来(常见:手势)控制,不同于系统缺省给定的push或者pop(非交互式) 

     */  

    return _interactionController;  

}  

  

#pragma mark - Transitioning Delegate (Modal)  

// 前2个用于动画  

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

{  

    self.minToMaxAnimator.animationType = AnimationTypePresent;  

    return _minToMaxAnimator;  

}  

  

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

{  

    self.minToMaxAnimator.animationType = AnimationTypeDismiss;  

    return _minToMaxAnimator;  

}  

  

// 后2个用于交互  

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

{  

    return _interactionController;  

}  

  

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

{  

    return nil;  

}  

以上实现的是非交互的转场,指的是完全按照系统指定的切换机制,用户无法中途取消或者控制进度切换.那怎么来实现交互转场呢:
UIPercentDrivenInteractiveTransition实现了UIViewControllerInteractiveTransitioning接口的类,,可以用一个百分比来控制交互式切换的过程。我们在手势识别中只需要告诉这个类的实例当前的状态百分比如何,系统便根据这个百分比和我们之前设定的迁移方式为我们计算当前应该的UI渲染,十分方便。具体的几个重要方法:
-(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
-(void)cancelInteractiveTransition 报告交互取消,返回切换前的状态
–(void)finishInteractiveTransition 报告交互完成,更新到切换后的状态

#pragma mark - 手势交互的主要实现--->UIPercentDrivenInteractiveTransition  

- (void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer  

{  

    UIView* view = self.view;  

    if (recognizer.state == UIGestureRecognizerStateBegan) {  

        // 获取手势的触摸点坐标  

        CGPoint location = [recognizer locationInView:view];  

        // 判断,用户从右半边滑动的时候,推出下一个VC(根据实际需要是推进还是推出)  

        if (location.x > CGRectGetMidX(view.bounds) && self.navigationController.viewControllers.count == 1){  

            self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];  

            //  

            [self presentViewController:_nextVC animated:YES completion:nil];  

        }  

    } else if (recognizer.state == UIGestureRecognizerStateChanged) {  

        // 获取手势在视图上偏移的坐标  

        CGPoint translation = [recognizer translationInView:view];  

        // 根据手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走  

        CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));  

        // 交互控制器控制动画的进度  

        [self.interactionController updateInteractiveTransition:distance];  

    } else if (recognizer.state == UIGestureRecognizerStateEnded) {  

        CGPoint translation = [recognizer translationInView:view];  

        // 根据手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走  

        CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));  

        // 移动超过一半就强制完成  

        if (distance > 0.5) {  

            [self.interactionController finishInteractiveTransition];  

        } else {  

            [self.interactionController cancelInteractiveTransition];  

        }  

        // 结束后一定要置为nil  

        self.interactionController = nil;  

    }  

}  

最后,给大家分享一个动画特效:类似于飞兔云传的发送ViewController切换

@implementation PDTransitionAnimator  

  

#define Switch_Time 1.2  

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

    return Switch_Time;  

}  

  

#define Button_Width 50.f  

#define Button_Space 10.f  

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

    UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  

    UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];  

  

    UIView * toView = toViewController.view;  

    UIView * fromView = fromViewController.view;  

      

    if (self.animationType == AnimationTypeDismiss) {  

        // 这个方法能够高效的将当前显示的view截取成一个新的view.你可以用这个截取的view用来显示.例如,也许你只想用一张截图来做动画,毕竟用原始的view做动画代价太高.因为是截取了已经存在的内容,这个方法只能反应出这个被截取的view当前的状态信息,而不能反应这个被截取的view以后要显示的信息.然而,不管怎么样,调用这个方法都会比将view做成截图来加载效率更高.  

        UIView * snap = [toView snapshotViewAfterScreenUpdates:YES];  

        [transitionContext.containerView addSubview:snap];  

        [snap setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width - Button_Width - Button_Space, [UIScreen mainScreen].bounds.size.height - Button_Width - Button_Space, Button_Width, Button_Width)];  

          

        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{  

            [snap setFrame:[UIScreen mainScreen].bounds];  

        } completion:^(BOOL finished) {  

            [UIView animateWithDuration:0.5 animations:^{  

                [[transitionContext containerView] addSubview:toView];  

                snap.alpha = 0;  

            } completion:^(BOOL finished) {  

                [snap removeFromSuperview];  

                [transitionContext completeTransition:![transitionContext transitionWasCancelled]];  

            }];  

        }];  

    } else {  

        UIView * snap2 = [toView snapshotViewAfterScreenUpdates:YES];  

        [transitionContext.containerView addSubview:snap2];  

        UIView * snap = [fromView snapshotViewAfterScreenUpdates:YES];  

        [transitionContext.containerView addSubview:snap];  

          

        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{  

            [snap setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width - Button_Width - Button_Space+ (Button_Width/2), [UIScreen mainScreen].bounds.size.height - Button_Width - Button_Space + (Button_Width/2), 0, 0)];  

        } completion:^(BOOL finished) {  

            [UIView animateWithDuration:0.5 animations:^{  

                //snap.alpha = 0;  

            } completion:^(BOOL finished) {  

                [snap removeFromSuperview];  

                [snap2 removeFromSuperview];  

                [[transitionContext containerView] addSubview:toView];  

                // 切记不要忘记了噢  

                [transitionContext completeTransition:![transitionContext transitionWasCancelled]];  

            }];  

        }];  

          

    }  

}  

其中,snapshotViewAfterScreenUpdates 方法的解释,我也不是很懂,反正初级来说会用就行,还可以参照下面的解析:
在iOS7 以前, 获取一个UIView的快照有以下步骤: 首先创建一个UIGraphics的图像上下文,然后将视图的layer渲染到该上下文中,从而取得一个图像,最后关闭图像上下文,并将图像显示在UIImageView中。现在我们只需要一行代码就可以完成上述步骤了:
[view snapshotViewAfterScreenUpdates:NO]; 

这个方法制作了一个UIView的副本,如果我们希望视图在执行动画之前保存现在的外观,以备之后使用(动画中视图可能会被子视图遮盖或者发生其他一些变化),该方法就特别方便。
afterUpdates参数表示是否在所有效果应用在视图上了以后再获取快照。例如,如果该参数为NO,则立马获取该视图现在状态的快照,反之,以下代码只能得到一个空白快照:
[view snapshotViewAfterScreenUpdates:YES]; 

[view setAlpha:0.0]; 

由于我们设置afterUpdates参数为YES,而视图的透明度值被设置成了0,所以方法将在该设置应用在视图上了之后才进行快照,于是乎屏幕空空如也。另外就是……你可以对快照再进行快照……继续快照……

继续前面的内容,这一章,主要介绍自定义ViewController容器上视图VC的切换.先来看看系统给我们提供的容器控制器 UINavigationController和UITabBarController 都有一个NSArray类型的属性viewControllers,很明显,存储的就是需要切换的视图VC.同理,我们定义一个ContainerViewController,是UIViewController的直接子类,用来作为容器依托,额,其他属性定义详见代码吧,这里不多说了.(PS:原先我进行多个自定义视图VC切换的方法,是放置一个UIScrollView,然后把所有childViewController的View的frame的X坐标,依此按320递增,大家可以自行想象下,这样不好的地方,我感觉就是所有的VC一经加载就全部实体化了,而且不会因为被切换变成暂不显示而释放掉)
偷懒下,用storyboard创建的5个childVC


// ContainerViewController  

@interface FTContainerViewController ()   

  

@property (strong, nonatomic) FTPhotoSenderViewController       *photoSenderViewController;  

@property (strong, nonatomic) FTVideoSenderViewController       *videoSenderViewController;  

@property (strong, nonatomic) FTFileSenderViewController        *fileSenderViewController;  

@property (strong, nonatomic) FTContactSenderViewController     *contactSenderViewController;  

@property (strong, nonatomic) FTClipboardSenderViewController   *clipboardSenderViewController;  

@property (strong, nonatomic) UIViewController                  *selectedViewController;        // 当前选择的VC  

@property (strong, nonatomic) NSArray                           *viewControllers;               // childVC数组  

@property (assign, nonatomic) NSInteger                         currentControllerIndex;         // 当前选择的VC的数组下标号  

  

  

@end  

  

@implementation FTContainerViewController  

  

#pragma mark - ViewLifecycle Methods  

  

- (void)viewDidLoad  

{  

    [super viewDidLoad];  

    // childVC  

    self.photoSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTPhotoSenderViewController"];  

    self.videoSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTVideoSenderViewController"];  

    self.fileSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTFileSenderViewController"];  

    self.contactSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTContactSenderViewController"];  

    self.clipboardSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"FTClipboardSenderViewController"];  

    // 存储childVC的数组  

    self.viewControllers = @[_photoSenderViewController,_videoSenderViewController,_fileSenderViewController,_contactSenderViewController,_clipboardSenderViewController];  

    // 缺省为下标为0的VC  

    self.selectedViewController = self.selectedViewController ?: self.viewControllers[0];  

    self.currentControllerIndex = 0;  

}  


依旧,实现UIViewControllerAnimatedTransitioning协议的Animator类,不过里面换个动画效果,利用iOS7新增的弹簧动画效果:
 

#import "FTMthTransitionAnimator.h"  

  

@implementation FTMthTransitionAnimator  

  

static CGFloat const kChildViewPadding = 16;  

static CGFloat const kDamping = 0.5;    // damping参数代表弹性阻尼,随着阻尼值越来越接近0.0,动画的弹性效果会越来越明显,而如果设置阻尼值为1.0,则视图动画不会有弹性效果  

static CGFloat const kInitialSpringVelocity = 0.5;  // 初始化弹簧速率  

  

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

{  

    return 1.0;  

}  

  

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

{  

    /** 

     *  - viewControllerForKey:我们可以通过他访问过渡的两个 ViewController。 

     *  - containerView:两个 ViewController 的 containerView。 

     *  - initialFrameForViewController 和 finalFrameForViewController 是过渡开始和结束时每个 ViewController 的 frame。 

     */  

    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];  

    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  

      

    [[transitionContext containerView] addSubview:toViewController.view];  

    toViewController.view.alpha = 0;  

    BOOL goingRight = ([transitionContext initialFrameForViewController:toViewController].origin.x < [transitionContext finalFrameForViewController:toViewController].origin.x);  

    CGFloat transDistance = [transitionContext containerView].bounds.size.width + kChildViewPadding;  

    CGAffineTransform transform = CGAffineTransformMakeTranslation(goingRight ? transDistance : -transDistance, 0);  

    // CGAffineTransformInvert 反转  

    toViewController.view.transform = CGAffineTransformInvert(transform);  

//    toViewController.view.transform = CGAffineTransformTranslate(toViewController.view.transform, (goingRight ? transDistance : -transDistance), 0);  

      

    /** 

     *   ----------弹簧动画.....------- 

     *  使用由弹簧的运动描述的时序曲线` animations` 。当` dampingRatio`为1时,动画将平稳减速到其最终的模型值不会振荡。阻尼比小于1来完全停止前将振荡越来越多。可以使用弹簧的初始速度,以指定的速度在模拟弹簧的端部的物体被移动它附着之前。这是一个单元坐标系,其中1是指行驶总距离的动画在第二。所以,如果你改变一个物体的位置由200PT在这个动画,以及你想要的动画表现得好像物体在动,在100PT /秒的动画开始之前,你会通过0.5 。你通常会想通过0的速度。 

     */  

    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:kDamping initialSpringVelocity:kInitialSpringVelocity options:0x00 animations:^{  

        fromViewController.view.transform = transform;  

        fromViewController.view.alpha = 0;  

        // CGAffineTransformIdentity  重置,初始化  

        toViewController.view.transform = CGAffineTransformIdentity;  

        toViewController.view.alpha = 1;  

    } completion:^(BOOL finished) {  

        fromViewController.view.transform = CGAffineTransformIdentity;  

        // 声明过渡结束-->记住,一定别忘了在过渡结束时调用 completeTransition: 这个方法。  

        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];  

    }];  

}  

  

@end  

接下来的代码,就是实现自定义容器切换的关键了.通常情况下,当我们使用系统内建的类时,系统框架为我们创建了转场上下文对象,并把它传递给动画控制器。但是在我们这种情况下,我们需要自定义转场动画,所以我们需要承担系统框架的责任,自己去创建这个转场上下文对象。


@interface FTMthTransitionContext : NSObject <UIViewControllerContextTransitioning>  

  

- (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight;  

@property (nonatomic, copy) void (^completionBlock)(BOOL didComplete);  

@property (nonatomic, assign, getter=isAnimated) BOOL animated;  

@property (nonatomic, assign, getter=isInteractive) BOOL interactive; // 是否交互式  

  

@property (nonatomic, strong) NSDictionary *privateViewControllers;  

@property (nonatomic, assign) CGRect privateDisappearingFromRect;  

@property (nonatomic, assign) CGRect privateAppearingFromRect;  

@property (nonatomic, assign) CGRect privateDisappearingToRect;  

@property (nonatomic, assign) CGRect privateAppearingToRect;  

@property (nonatomic, weak) UIView *containerView;  

@property (nonatomic, assign) UIModalPresentationStyle presentationStyle;  

  

@end  

  

@implementation FTMthTransitionContext  

  

- (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight {  

      

    if ((self = [super init])) {  

        self.presentationStyle = UIModalPresentationCustom;  

        self.containerView = fromViewController.view.superview;  

        self.privateViewControllers = @{  

                                        UITransitionContextFromViewControllerKey:fromViewController,  

                                        UITransitionContextToViewControllerKey:toViewController,  

                                        };  

          

        // Set the view frame properties which make sense in our specialized ContainerViewController context. Views appear from and disappear to the sides, corresponding to where the icon buttons are positioned. So tapping a button to the right of the currently selected, makes the view disappear to the left and the new view appear from the right. The animator object can choose to use this to determine whether the transition should be going left to right, or right to left, for example.  

        CGFloat travelDistance = (goingRight ? -self.containerView.bounds.size.width : self.containerView.bounds.size.width);  

        self.privateDisappearingFromRect = self.privateAppearingToRect = self.containerView.bounds;  

        self.privateDisappearingToRect = CGRectOffset (self.containerView.bounds, travelDistance, 0);  

        self.privateAppearingFromRect = CGRectOffset (self.containerView.bounds, -travelDistance, 0);  

    }  

      

    return self;  

}  

  

- (CGRect)initialFrameForViewController:(UIViewController *)viewController {  

    if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {  

        return self.privateDisappearingFromRect;  

    } else {  

        return self.privateAppearingFromRect;  

    }  

}  

  

- (CGRect)finalFrameForViewController:(UIViewController *)viewController {  

    if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {  

        return self.privateDisappearingToRect;  

    } else {  

        return self.privateAppearingToRect;  

    }  

}  

  

- (UIViewController *)viewControllerForKey:(NSString *)key {  

    return self.privateViewControllers[key];  

}  

  

- (void)completeTransition:(BOOL)didComplete {  

    if (self.completionBlock) {  

        self.completionBlock (didComplete);  

    }  

}  

  

// 非交互式,直接返回NO,因为不允许交互当然也就无法操作进度取消  

- (BOOL)transitionWasCancelled { return NO; }  

  

// 非交互式,直接不进行操作,只有进行交互,下面3个协议方法才有意义,可参照系统给我们定义好的交互控制器  

// @interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>  

- (void)updateInteractiveTransition:(CGFloat)percentComplete {}  

- (void)finishInteractiveTransition {}  

- (void)cancelInteractiveTransition {}  

  

@end  

OK,准备工作都做好了,为了仿照UIScrollView的滑动切换,但又因为现在展示的是非交互式,我们定义一个swip(轻扫)手势.


UISwipeGestureRecognizer *leftGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapController:)];  

[leftGesture setDirection:UISwipeGestureRecognizerDirectionLeft];  

[self.view addGestureRecognizer:leftGesture];  

  

UISwipeGestureRecognizer *rightGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapController:)];  

[rightGesture setDirection:UISwipeGestureRecognizerDirectionRight];  

[self.view addGestureRecognizer:rightGesture];  

[objc] view plaincopy

// 响应手势的方法  

- (void)swapViewControllers:(UISwipeGestureRecognizer *)swipeGestureRecognizer  

{  

    if (swipeGestureRecognizer.direction == UISwipeGestureRecognizerDirectionLeft) {  

        if (_currentControllerIndex < 4) {  

            _currentControllerIndex++;  

        }  

        NSLog(@"_currentControllerIndex = %ld",(long)_currentControllerIndex);  

        UIViewController *selectedViewController = self.viewControllers[_currentControllerIndex];  

         NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"右边");  

         self.selectedViewController = selectedViewController;  

    } else if (swipeGestureRecognizer.direction == UISwipeGestureRecognizerDirectionRight){  

         NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"左边");  

        if (_currentControllerIndex > 0) {  

            _currentControllerIndex--;  

        }  

        UIViewController *selectedViewController = self.viewControllers[_currentControllerIndex];  

        self.selectedViewController = selectedViewController;  

    }  

}  

  

// 重写selectedViewController的setter  

- (void)setSelectedViewController:(UIViewController *)selectedViewController  

{  

    NSParameterAssert (selectedViewController);  

    [self _transitionToChildViewController:selectedViewController];  

    _selectedViewController = selectedViewController;  

}  

  

// 切换操作(自定义的,联想我在前面文章网易标签栏切换中,系统给的transitionFromViewController,是一个道理)  

- (void)_transitionToChildViewController:(UIViewController *)toViewController  

{  

    UIViewController *fromViewController = self.childViewControllers.count > 0 ? self.childViewControllers[0] : nil;  

    if (toViewController == fromViewController) {  

        return;  

    }  

    UIView *toView = toViewController.view;  

    [toView setTranslatesAutoresizingMaskIntoConstraints:YES];  

    toView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;  

    toView.frame = self.view.bounds;  

      

    // 自定义容器的切换,addChildViewController是关键,它保证了你想要显示的VC能够加载到容器中  

    // 而所谓的动画和上下文,只是为了转场的动画效果  

    // 因此,就算用UIScrollView切换,也不能缺少addChildViewController,切记!切记!  

    [fromViewController willMoveToParentViewController:nil];  

    [self addChildViewController:toViewController];  

      

    if (!fromViewController) {  

        [self.view addSubview:toViewController.view];  

        [toViewController didMoveToParentViewController:self];  

        return;  

    }  

      

    // Animator  

    FTMthTransitionAnimator *transitionAnimator = [[FTMthTransitionAnimator alloc] init];  

    NSUInteger fromIndex = [self.viewControllers indexOfObject:fromViewController];  

    NSUInteger toIndex = [self.viewControllers indexOfObject:toViewController];  

      

    // Context  

    FTMthTransitionContext *transitionContext = [[FTMthTransitionContext alloc] initWithFromViewController:fromViewController toViewController:toViewController goingRight:(toIndex > fromIndex)];  

    transitionContext.animated = YES;  

    transitionContext.interactive = NO;  

    transitionContext.completionBlock = ^(BOOL didComplete) {  

        // 因为是非交互式,所以fromVC可以直接直接remove出its parent's children controllers array  

        [fromViewController.view removeFromSuperview];  

        [fromViewController removeFromParentViewController];  

        [toViewController didMoveToParentViewController:self];  

        if ([transitionAnimator respondsToSelector:@selector (animationEnded:)]) {  

            [transitionAnimator animationEnded:didComplete];  

        }  

    };  

    // 转场动画需要以转场上下文为依托,因为我们是自定义的Context,所以要手动设置  

    [transitionAnimator animateTransition:transitionContext];  

} 


大功告成.
上面展示的就是一个基本的自定义容器的非交互式的转场切换.那交互式的呢?从上面我定义手势定义为swip而不是pan也可以看出,非交互转场,并不能完全实现UIScrollView那种分页式的效果,按照类似百分比的形式来进行fromVC和toVC的切换,因为我们缺少交互控制器.在自定义的容器中,系统是没有提供返回交互控制器的协议给我们的,查了蛮多资料,也没找到给出明确的方法,我认为,要跟实现转场上下文一样,仿照系统方法,自定义的去实现交互式的协议方法.我们就要去思考,系统是如何搭建起这个环境的.

 类似资料:
  • 本文向大家介绍iOS开发tips-UINavigationBar的切换效果,包括了iOS开发tips-UINavigationBar的切换效果的使用技巧和注意事项,需要的朋友参考一下 概述 在iOS系统中,如果控制器是以push方式进行管理的话,那么事实上多个控制器是共享的同一个导航栏。当然iOS系统的设计无可厚非,但是国内的应用经常会遇到很多个性的设计,就比如说A push到 B,A可能有导航栏

  • 本文向大家介绍Android开发中ViewPager实现多页面切换效果,包括了Android开发中ViewPager实现多页面切换效果的使用技巧和注意事项,需要的朋友参考一下 ViewPager用于实现多页面的切换效果,该类存在于Google的兼容包里面,所以在引用时记得在BuilldPath中加入“Android-support-v4.jar” 首先必须知道:要使用ViewPager,必须要使用

  • 本文向大家介绍简单实现JavaScript图片切换效果,包括了简单实现JavaScript图片切换效果的使用技巧和注意事项,需要的朋友参考一下 JavaScript实现图片切换,主要用到setInterval()函数和clearInterval()函数,前者功能是开启动画,后者功能则为清除动画(可理解为使动画停止),为了使动画停止,则需要定义全局变量作为标志,标志返回setInterval()函数

  • 本文向大家介绍简单实现js菜单栏切换效果,包括了简单实现js菜单栏切换效果的使用技巧和注意事项,需要的朋友参考一下 分享一个小案例,实现菜单栏的切换,点击左侧边栏,展示右侧主体的页面,供大家参考,具体内容如下 首先实现html页面的编写: 其次是css效果实现: 最后一步运用简单的js实现点击左侧边栏选项,展示右侧主体区域: 综上一个简单的菜单切换就实现了。 更多菜单效果点击《JavaScript

  • 本文向大家介绍jQuery简单实现tab选项卡切换效果,包括了jQuery简单实现tab选项卡切换效果的使用技巧和注意事项,需要的朋友参考一下 抽空把公司项目上用的tab效果封装了一下,实在是需要用的地方太多了~~~ 效果图: 代码: 以上就是本文的全部内容,希望能给大家一个参考,也希望大家多多支持呐喊教程。

  • 本文向大家介绍label+input实现按钮开关切换效果的实例,包括了label+input实现按钮开关切换效果的实例的使用技巧和注意事项,需要的朋友参考一下 代码如下所示: 以上这篇label+input实现按钮开关切换效果的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持呐喊教程。