自定义过渡动画

各视图控制器之间的切换,大概可分为三种

  • UITabBarController 他的子视图控制器被选中时,切换子视图
  • UINavigationController push/pop一个子视图时
  • 一个UIViewController 被presented/dismissed时
自定义非交互式过渡动画 通用部分
1、所要实现自定义过渡动画的ViewController实现对应的delegate,UITabBarViewController对应的是UITabBarControllerDelegate,UINavigationController对应的是UINavigationControllerDelegate
2、在动画开始时,delegate会调用对应的动画控制器,也就是实现了UIViewControllerAnimatedTransitioning协议的类,这个类可以是任意类型。返回nil,则使用默认动画效果。
3、动画控制器实现对应的方法
  • transitionDuration:
    动画执行时间
  • animateTransition:
    实现自定义动画的地方
其他解释在代码注释中
实现一个简单的例子,将UITabBarController的切换视图方式改成左右移动的方式
Swift版本:
只是个简单的例子,所以建立一个 Tabbled Application 模板的项目
实现AppDelegate即可
import UIKit@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. (self.window!.rootViewController as! UITabBarController).delegate = self return true } }extension AppDelegate : UITabBarControllerDelegate{ func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self }}extension AppDelegate : UIViewControllerAnimatedTransitioning{//动画的执行时间,需要注意的是一定要和animateTransition:里的动画时间保持一致 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.4 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!let containerView = transitionContext.containerView()!//这个方法是iOS8之后才有的 let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!//iOS8之前取得的方式 //let view11 = vc1.view //let view22 = vc2.viewlet r1start = transitionContext.initialFrameForViewController(vc1) let r2end = transitionContext.finalFrameForViewController(vc2)//which key we are going depends on which vc is which the most general way to express this is in terms of index number let tab = self.window!.rootViewController as! UITabBarControllerlet index1 = tab.viewControllers!.indexOf(vc1) let index2 = tab.viewControllers!.indexOf(vc2) let dir : CGFloat = index1 < index2 ? 1 : -1 var r1end = r1start r1end.origin.x -= r1end.width * dirvar r2start = r2end r2start.origin.x += r2start.width * dir view2.frame = r2start containerView.addSubview(view2)//避免其他手势事件的干扰 UIApplication.sharedApplication().beginIgnoringInteractionEvents() //执行具体动画,不同效果的代码不同处就是在这了 UIView.animateWithDuration(0.4, animations: { view1.frame = r1end view2.frame = r2end }) { (_) in //防止取消状态 transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } }func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } } }

为了让代码显得整洁一些,并且可以是该动画可以在多出更方便的使用,我们将其独立出来。
import UIKit@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. (self.window!.rootViewController as! UITabBarController).delegate = self return true }

}
extension AppDelegate : UITabBarControllerDelegate{ func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { let animation = CustomTransitionAimation() animation.tab = (self.window!.rootViewController as! UITabBarController) return animation } }

动画类的实现
import UIKitclass CustomTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning { var tab : UITabBarController?//动画的执行时间,需要注意的是一定要和animateTransition:里的动画时间保持一致 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.4 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!let containerView = transitionContext.containerView()!//这个方法是iOS8之后才有的 let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!//iOS8之前取得的方式 //let view11 = vc1.view //let view22 = vc2.viewlet r1start = transitionContext.initialFrameForViewController(vc1) let r2end = transitionContext.finalFrameForViewController(vc2)//which key we are going depends on which vc is which the most general way to express this is in terms of index number let tab = self.tab! let index1 = tab.viewControllers!.indexOf(vc1) let index2 = tab.viewControllers!.indexOf(vc2) let dir : CGFloat = index1 < index2 ? 1 : -1 var r1end = r1start r1end.origin.x -= r1end.width * dirvar r2start = r2end r2start.origin.x += r2start.width * dir view2.frame = r2start containerView.addSubview(view2)//避免其他手势事件的干扰 UIApplication.sharedApplication().beginIgnoringInteractionEvents() //执行具体动画,不同效果的代码不同处就是在这了 UIView.animateWithDuration(0.4, animations: { view1.frame = r1end view2.frame = r2end }) { (_) in //防止取消状态 transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } }func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } }}

Objective-C版本
#import "AppDelegate.h" #import "CusomTransitionAnimation.h"@interface AppDelegate ()@property(nonatomic,strong) UITabBarController *tabBarController; @property(nonatomic,strong) CusomTransitionAnimation *customAnimation; @end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.tabBarController = (UITabBarController *)self.window.rootViewController; self.tabBarController.delegate = self; return YES; }- (id )tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { CusomTransitionAnimation *animation = [[CusomTransitionAnimation alloc]init]; animation.tabBarController = self.tabBarController; returnanimation; }- (CusomTransitionAnimation *)customAnimation { if (!_customAnimation) { _customAnimation = [[CusomTransitionAnimation alloc]init]; _customAnimation.tabBarController = self.tabBarController; } return _customAnimation; }@end

动画类
//.h #import #import @interface CusomTransitionAnimation : NSObject@property(nonatomic,strong) UITabBarController *tabBarController; @end//.m #import "CusomTransitionAnimation.h"@implementation CusomTransitionAnimation- (NSTimeInterval)transitionDuration:(nullable id )transitionContext { return 0.4; } - (void)animateTransition:(id )transitionContext { UIView *containerView = [transitionContext containerView]; UIViewController *vc1 = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *vc2 = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *view1 = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *view2 = [transitionContext viewForKey:UITransitionContextToViewKey]; CGRect vc1start = [transitionContext initialFrameForViewController:vc1]; CGRect vc2end = [transitionContext finalFrameForViewController:vc2]; NSUInteger index1 = [self.tabBarController.viewControllers indexOfObject:vc1]; NSUInteger index2 = [self.tabBarController.viewControllers indexOfObject:vc2]; int dir = index1 < index2 ? 1 : -1; CGRect vc1end = vc1start; vc1end.origin.x -= vc1end.size.width * dir; CGRect vc2start = vc2end; vc2start.origin.x += vc2start.size.width * dir; view2.frame = vc2start; [containerView addSubview:view2]; [[UIApplication sharedApplication]beginIgnoringInteractionEvents]; [UIView animateWithDuration:0.4 animations:^{ view1.frame = vc1end; view2.frame = vc2end; } completion:^(BOOL finished) { [transitionContext completeTransition:(!transitionContext.transitionWasCancelled)]; [[UIApplication sharedApplication]endIgnoringInteractionEvents]; }]; }- (void)animationEnded:(BOOL)transitionCompleted { if (transitionCompleted) { NSLog(@"Completed"); }else{ NSLog(@"No Completed"); } } @end

UINavigationController实现是类似的,只不过调用动画类的方式不同
viewController实现
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.navigationController!.delegate = self }@IBAction func pushAction(sender: AnyObject) { let second = SecondViewController() self.navigationController!.pushViewController(second, animated: true) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }extension ViewController : UINavigationControllerDelegate{ func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { //operation 用来区分是push还是pop操作 if operation == .Push { return CustomPushTransitionAimation() } else if operation == .Pop{ return CustomPopTransitionAimation() } return nil } }

Push动画
import UIKitclass CustomPushTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {//动画的执行时间,需要注意的是一定要和animateTransition:里的动画时间保持一致 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.4 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { //let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! //let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!let containerView = transitionContext.containerView()!//这个方法是iOS8之后才有的 let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!//iOS8之前取得的方式 //let view11 = vc1.view //let view22 = vc2.viewview2.transform = CGAffineTransformMakeScale(0.1, 0.1) containerView.addSubview(view2)//避免其他手势事件的干扰 UIApplication.sharedApplication().beginIgnoringInteractionEvents() //执行具体动画,不同效果的代码不同处就是在这了 UIView.animateWithDuration(0.4, animations: { view2.transform = CGAffineTransformIdentity }) { (_) in //防止取消状态 transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } }func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } }

}
Pop动画
import UIKitclass CustomPushTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {//动画的执行时间,需要注意的是一定要和animateTransition:里的动画时间保持一致 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.4 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { //let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! //let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!let containerView = transitionContext.containerView()!//这个方法是iOS8之后才有的 let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!//iOS8之前取得的方式 //let view11 = vc1.view //let view22 = vc2.viewview2.transform = CGAffineTransformMakeScale(0.1, 0.1) containerView.addSubview(view2)//避免其他手势事件的干扰 UIApplication.sharedApplication().beginIgnoringInteractionEvents() //执行具体动画,不同效果的代码不同处就是在这了 UIView.animateWithDuration(0.4, animations: { view2.transform = CGAffineTransformIdentity }) { (_) in //防止取消状态 transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } }func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } } }

可交互的自定义过渡动画 有两种实现方式
使用百分比驱动 使用这种方式比较简单,还是按照上述的方法先实现不可交互的自定义动画。接下来实现回调方法
func tabBarController(tabBarController: UITabBarController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

主要是UIPercentDrivenInteractiveTransition的使用,实质是frozen animation。
import UIKit@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?var rightEdgr : UIScreenEdgePanGestureRecognizer! var leftEdgr : UIScreenEdgePanGestureRecognizer! var inter : UIPercentDrivenInteractiveTransition!//这个类是关键 var interacting = falsefunc application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. let tab = (self.window!.rootViewController as! UITabBarController) tab.delegate = selflet sep = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(pan)) sep.edges = .Right sep.delegate = self tab.view.addGestureRecognizer(sep) self.rightEdgr = seplet sep2 = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(pan)) sep2.edges = .Left sep2.delegate = self tab.view.addGestureRecognizer(sep2) self.leftEdgr = sep2return true }

}
extension AppDelegate : UITabBarControllerDelegate{ func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { //let animation = CustomTransitionAimation() //animation.tab = (self.window!.rootViewController as! UITabBarController) //return animation return self }func tabBarController(tabBarController: UITabBarController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if self.interacting { return self.inter } return nil } }extension AppDelegate : UIGestureRecognizerDelegate{func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { let tab = self.window!.rootViewController as! UITabBarController var result = falseif gestureRecognizer == self.rightEdgr { result = (tab.selectedIndex < tab.viewControllers!.count - 1) } else{ result = tab.selectedIndex > 0 } return result }func pan(g : UIScreenEdgePanGestureRecognizer) -> Void { //重点需要处理的地方 let v = g.view! let tab = self.window!.rootViewController as! UITabBarController let delta = g.translationInView(v) let percent = fabs(delta.x/v.bounds.width) switch g.state { case .Began: self.inter = UIPercentDrivenInteractiveTransition() self.interacting = true if g == self.rightEdgr { tab.selectedIndex = tab.selectedIndex + 1 }else{ tab.selectedIndex = tab.selectedIndex - 1 } print("selectedIndex = \(tab.selectedIndex)") case .Changed: self.inter.updateInteractiveTransition(percent) case .Ended: if percent > 0.5 { self.inter.finishInteractiveTransition()//调用该方法,将快速结束之前所设置的操作 }else{ self.inter.cancelInteractiveTransition()//调用该方法,将恢复原状 } self.interacting = false case .Cancelled: self.inter.cancelInteractiveTransition() self.interacting = false default : break }} }extension AppDelegate : UIViewControllerAnimatedTransitioning{//动画的执行时间,需要注意的是一定要和animateTransition:里的动画时间保持一致 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.4 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!let containerView = transitionContext.containerView()!//这个方法是iOS8之后才有的 let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!//iOS8之前取得的方式 //let view11 = vc1.view //let view22 = vc2.viewlet r1start = transitionContext.initialFrameForViewController(vc1) let r2end = transitionContext.finalFrameForViewController(vc2)//which key we are going depends on which vc is which the most general way to express this is in terms of index number let tab = self.window!.rootViewController as! UITabBarControllerlet index1 = tab.viewControllers!.indexOf(vc1) let index2 = tab.viewControllers!.indexOf(vc2) let dir : CGFloat = index1 < index2 ? 1 : -1 var r1end = r1start r1end.origin.x -= r1end.width * dirvar r2start = r2end r2start.origin.x += r2start.width * dir view2.frame = r2start containerView.addSubview(view2) /* //避免其他手势事件的干扰 UIApplication.sharedApplication().beginIgnoringInteractionEvents() //执行具体动画,不同效果的代码不同处就是在这了 UIView.animateWithDuration(0.4, animations: { view1.frame = r1end view2.frame = r2end }) { (_) in //防止取消状态 transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } */let opts : UIViewAnimationOptions = self.interacting ? .CurveLinear : [] if !self.interacting { UIApplication.sharedApplication().beginIgnoringInteractionEvents() } UIView.animateWithDuration(0.4, delay: 0, options: opts, animations: { view1.frame = r1end view2.frame = r2end }) { (_) in let canceld = transitionContext.transitionWasCancelled() transitionContext.completeTransition(!canceld) if UIApplication.sharedApplication().isIgnoringInteractionEvents(){ UIApplication.sharedApplication().endIgnoringInteractionEvents() } } }func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") }let tab = self.window!.rootViewController as! UITabBarController print(tab.selectedIndex)} }

不使用百分比驱动 这种写法虽然看起来会稍微复杂一些,但是他的可定制程度更高,因为我们可以直接对fromView 和 toView等进行直接操作。
不在需要具体实现animateTransition方法,但是他还是要存在的,因为他的协议必须实现的方法。
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {}

将其中的代码放到实现了协议UIViewControllerInteractiveTransitioning的类中实现。放到方法
func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning){ }

中就可以了。
这次使用OC实现,都熟悉下:
#import "AppDelegate.h"@interface AppDelegate ()@property(nonatomic,strong) idtransitionContext; @property(nonatomic,strong) UIScreenEdgePanGestureRecognizer *leftEdgr; @property(nonatomic,strong) UIScreenEdgePanGestureRecognizer *rightEdgr; @property(nonatomic,assign) BOOL interacting; @property(nonatomic,assign) CGRect r1end; @property(nonatomic,assign) CGRect r2start; @end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch.UITabBarController *tab = (UITabBarController *)self.window.rootViewController; tab.delegate = self; self.leftEdgr = ({ UIScreenEdgePanGestureRecognizer *pan = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; pan.edges = UIRectEdgeLeft; pan.delegate = self; [tab.view addGestureRecognizer:pan]; pan; }); self.rightEdgr = ({ UIScreenEdgePanGestureRecognizer *pan = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; pan.edges = UIRectEdgeRight; pan.delegate = self; [tab.view addGestureRecognizer:pan]; pan; }); self.r1end = CGRectZero; self.r2start = CGRectZero; return YES; }#pragma mark - Actions- (void)pan:(UIScreenEdgePanGestureRecognizer *)ges { UIView *gesView = ges.view; UITabBarController *tab = (UITabBarController *)self.window.rootViewController; CGPoint delta = [ges translationInView:gesView]; CGFloat percent = delta.x/gesView.bounds.size.width; UIViewController *fromVC,*toVC; UIView *fromView,*toView; CGRect fromRectStart,toRectEnd; if (self.transitionContext) { fromVC = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; toVC = [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey]; toView = [self.transitionContext viewForKey:UITransitionContextToViewKey]; fromRectStart = [self.transitionContext initialFrameForViewController:fromVC]; toRectEnd = [self.transitionContext finalFrameForViewController:toVC]; }switch (ges.state) { case UIGestureRecognizerStateBegan: { self.interacting = YES; if (ges == self.leftEdgr) { tab.selectedIndex = tab.selectedIndex - 1; }else{ tab.selectedIndex = tab.selectedIndex + 1; } break; } case UIGestureRecognizerStateChanged:{ //可以在这里对fromView 和 toView 进行直接操作,所以,可定制程度高fromRectStart.origin.x += (self.r1end.origin.x - fromRectStart.origin.x) * percent; fromView.frame = fromRectStart; NSLog(@"fromRect : %@",NSStringFromCGRect(fromRectStart)); CGRect toRectStart = self.r2start; toRectStart.origin.x += (toRectStart.origin.x - self.r2start.origin.x) * percent; toView.frame = toRectStart; NSLog(@"toRect : %@",NSStringFromCGRect(toRectStart)); //在UITabBarController的切换中可能看不出来这句话有什么用处,但是在UINavigationController中这个就很有用了,可以自己处理NavigationBar的效果等。 [self.transitionContext updateInteractiveTransition:percent]; break; } case UIGestureRecognizerStateEnded: { if (percent > 0.5) { //达到要求,设置成最终状态 [UIView animateWithDuration:0.2 animations:^{ fromView.frame = self.r1end; toView.frame = toRectEnd; } completion:^(BOOL finished) { [self.transitionContext finishInteractiveTransition]; [self.transitionContext completeTransition:YES]; }]; } else{ //恢复原状 [UIView animateWithDuration:0.2 animations:^{ fromView.frame = fromRectStart; toView.frame = self.r2start; } completion:^(BOOL finished) { [self.transitionContext cancelInteractiveTransition]; [self.transitionContext completeTransition:NO]; }]; }self.interacting = NO; self.transitionContext = nil; break; } case UIGestureRecognizerStateCancelled: {//恢复原状 fromView.frame = fromRectStart; toView.frame = self.r2start; [self.transitionContext finishInteractiveTransition]; [self.transitionContext completeTransition:NO]; self.interacting = NO; self.transitionContext = nil; break; } default: break; }}#pragma mark - UIGestureRecognizerDelegate- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { UITabBarController *tab = (UITabBarController *)self.window.rootViewController; BOOL result = NO; if (gestureRecognizer == self.leftEdgr) { result = tab.selectedIndex > 0; }else{ result = tab.selectedIndex < tab.viewControllers.count - 1; }return result; }#pragma mark - UITabBarControllerDelegate- (nullable id )tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController: (id )animationController { return self.interacting ? self : nil; }- (nullable id )tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { return self.interacting ? self : nil; }#pragma mark - UIViewControllerAnimatedTransitioning- (NSTimeInterval)transitionDuration:(id)transitionContext { return 0.4; }//这个要实现,但是要保持为空 - (void)animateTransition:(id)transitionContext {}#pragma mark - UIViewControllerInteractiveTransitioning //这个其实就是要实现 animateTransition: 原来实现的功能 - (void)startInteractiveTransition:(id)transitionContext {self.transitionContext = transitionContext; //即最终要达到的状态 UIView *containerView = [transitionContext containerView]; UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; CGRect fromRectStart = [transitionContext initialFrameForViewController:fromVC]; CGRect toRectEnd = [transitionContext finalFrameForViewController:toVC]; UITabBarController *tab = (UITabBarController *)self.window.rootViewController; NSUInteger fromIndex = [tab.viewControllers indexOfObject:fromVC]; NSUInteger toIndex = [tab.viewControllers indexOfObject:toVC]; int dir = fromIndex < toIndex ? 1 : -1; CGRect fromRectEnd = fromRectStart; fromRectEnd.origin.x -= fromRectEnd.size.width * dir; CGRect toRectStart = toRectEnd; toRectStart.origin.x += toRectStart.size.width * dir; toView.frame = toRectStart; [containerView addSubview:toView]; self.r1end = fromRectEnd; self.r2start = toRectStart; }@end

自定义Presented ViewController的过渡动画 实现主要要两种方式
  • 不使用presentation controller
  • 使用presentation controller
不使用presentation controller 需要注意的地方是
  • 协议transitioningDelegate声明的位置
  • 区分present 和 dismiss
  • 只有当modalPresentationStyle *不是 .FullScreen *的时候,才能使用如下方式判定是present还是dismiss
【自定义过渡动画】上代码
import UIKitclass ViewController2: UIViewController {override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) //这个不能再 viewDidLoad 里执行,太晚了 self.transitioningDelegate = self }required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.transitioningDelegate = self } override func viewDidLoad() { super.viewDidLoad()// Do any additional setup after loading the view. self.view.backgroundColor = UIColor.blueColor() }override func touchesBegan(touches: Set, withEvent event: UIEvent?) { self.presentingViewController!.dismissViewControllerAnimated(true, completion: nil) }override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }extension ViewController2 : UIViewControllerTransitioningDelegate{ func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self }func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self } }extension ViewController2 : UIViewControllerAnimatedTransitioning{ func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.4 }func animateTransition(transitionContext: UIViewControllerContextTransitioning) {let containerView = transitionContext.containerView()!//For a presentation that is not .FullScreen,the unused view is nil. let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) let toView = transitionContext.viewForKey(UITransitionContextToViewKey)//distinguish the cases //dismiss if fromView != nil { UIView.animateWithDuration(0.4, animations: { fromView!.transform = CGAffineTransformMakeScale(0.1, 0.1) fromView!.alpha = 0 }, completion: { (_) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) }) } //presenting else if toView != nil{ let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let toRectEnd = transitionContext.finalFrameForViewController(toVC) toView!.frame = CGRectInset(toRectEnd, 40, 40) toView!.transform = CGAffineTransformMakeScale(0.1, 0.1) toView!.alpha = 0 containerView.addSubview(toView!) //also can modify the containerView //containerView.backgroundColor = UIColor.greenColor() UIView.animateWithDuration(0.4, animations: { toView!.transform = CGAffineTransformIdentity toView!.alpha = 1.0 }) { (_) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) } } } }

在.FullScreen 类型的presented中,会取得presenting View,并将其添加到containerView中,其他类型的则是,containerView是在presenting view之前的,不能给containerView添加动画,并且containerView不会移除。
使用Presentation Controller 在此之前,需要理解的一些东西
  • Animation Controller 用来负责动画效果,也就是presented View 如何移动到最终目标位置
  • Presentation Controller 确定presented View的最终位置,还有向containerView上添加一些视图,例如半透明效果什么的。
    举个例子来说明,如果只是实现了UIPresentationController 而没有做上面的那些工作,会使用默认动画弹出。
接下来说说实现:
在实现上面效果的基础上,需要在做几点工作
  • 在设置presented viewController的 transitioningDelegate属性的同时,还需要设置其的modalPresentationStyle为.Custom,这个是必须的。(注意代码位置)
    // NB if we want to modify the _animation_, we need to set the transitioningDelegate self.transitioningDelegate = self // if we want to modify the _presentation_, we need to set the style to custom // customize presentation only on iPhone // how will we find out which it is? we have no traitCollection yet... // I know, let's ask the window if UIApplication.sharedApplication().keyWindow!.traitCollection.userInterfaceIdiom == .Phone { self.modalPresentationStyle = .Custom }

  • 在实现另一个方法
    @available(iOS 8.0, *) optional public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?

接下来便是在 UIPresentationController的子类中重载他的方法来达到我们所要的效果
方法的说明:
// Position of the presented view in the container view by the end of the presentation transition. // (Default: container view bounds) public func frameOfPresentedViewInContainerView() -> CGRect

返回presented view的最终位置
使用下面的方法来给containerView添加或移除一些额外的视图
presentationTransitionWillBegin presentationTransitionDidEnd dismissalTransitionWillBegin dismissalTransitionDidEnd

使用下面方法来个添加的额外视图进行布局
containerViewWillLayoutSubviews containerViewDidLayoutSubviews

方法
shouldPresentInFullscreen

默认返回的是true,如果返回false,会将presentation改变为 .CurrentContext
主要就这几个,完结。
Transition Coordinator的使用
主要注意几个方法的使用就可以了
  • animateAlongsideTransition:completion:
    可以用来给已经在视图上的子视图添加动画效果
    - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (self.transitionCoordinator != nil) { [self.transitionCoordinator animateAlongsideTransition:^(id_Nonnull context) { NSLog(@"Transition Animation"); self.animationView.center = self.view.center; } completion:^(id_Nonnull context) { NSLog(@"Transition completion"); }]; } else{ NSLog(@"Transition is NULL"); } }

  • notifyWhenInteractionEndsUsingBlock:
监控视图返回时手势动作是否成功。比如在UINavigationController中左侧滑动返回时,需要在上一个ViewController中实现。
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (self.transitionCoordinator != nil) {[self.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id_Nonnull context) { if ([context isCancelled]) { NSLog(@"1 Interaction Cancelled"); return ; } NSLog(@"1 Interaction Ends"); }]; } else{ NSLog(@"1 Transition is NULL"); } }

    推荐阅读