自定義過渡動(dòng)畫

各視圖控制器之間的切換肥橙,大概可分為三種

  • UITabBarController 他的子視圖控制器被選中時(shí)涯塔,切換子視圖
  • UINavigationController push/pop一個(gè)子視圖時(shí)
  • 一個(gè)UIViewController 被presented/dismissed時(shí)

自定義非交互式過渡動(dòng)畫

通用部分
1每窖、所要實(shí)現(xiàn)自定義過渡動(dòng)畫的ViewController實(shí)現(xiàn)對(duì)應(yīng)的delegate扣唱,UITabBarViewController對(duì)應(yīng)的是UITabBarControllerDelegate篙悯,UINavigationController對(duì)應(yīng)的是UINavigationControllerDelegate矮冬。
2、在動(dòng)畫開始時(shí)柠偶,delegate會(huì)調(diào)用對(duì)應(yīng)的動(dòng)畫控制器情妖,也就是實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning協(xié)議的類,這個(gè)類可以是任意類型诱担。返回nil毡证,則使用默認(rèn)動(dòng)畫效果。
3蔫仙、動(dòng)畫控制器實(shí)現(xiàn)對(duì)應(yīng)的方法

  • transitionDuration:
    動(dòng)畫執(zhí)行時(shí)間
  • animateTransition:
    實(shí)現(xiàn)自定義動(dòng)畫的地方

其他解釋在代碼注釋中
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的例子料睛,將UITabBarController的切換視圖方式改成左右移動(dòng)的方式
Swift版本:
只是個(gè)簡(jiǎn)單的例子,所以建立一個(gè) Tabbled Application 模板的項(xiàng)目
實(shí)現(xiàn)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{

//動(dòng)畫的執(zhí)行時(shí)間摇邦,需要注意的是一定要和animateTransition:里的動(dòng)畫時(shí)間保持一致
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()!
    
    //這個(gè)方法是iOS8之后才有的
    let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
    let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
    
    //iOS8之前取得的方式
    //        let view11 = vc1.view
    //        let view22 = vc2.view
    
    
    let 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! UITabBarController
    
    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 * dir
    
    var r2start = r2end
    r2start.origin.x += r2start.width * dir
    view2.frame = r2start
    containerView.addSubview(view2)
    
    //避免其他手勢(shì)事件的干擾
    UIApplication.sharedApplication().beginIgnoringInteractionEvents()
    //執(zhí)行具體動(dòng)畫恤煞,不同效果的代碼不同處就是在這了
    UIView.animateWithDuration(0.4, animations: {
        view1.frame = r1end
        view2.frame = r2end
    }) { (_) in
        //防止取消狀態(tài)
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        UIApplication.sharedApplication().endIgnoringInteractionEvents()
    }
}

func animationEnded(transitionCompleted: Bool) {
    if transitionCompleted {
        print("Completed")
    }else{
        print("NO Completed")
    }
}
}

為了讓代碼顯得整潔一些,并且可以是該動(dòng)畫可以在多出更方便的使用施籍,我們將其獨(dú)立出來居扒。

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
}
}

動(dòng)畫類的實(shí)現(xiàn)

import UIKit

class CustomTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {
var tab : UITabBarController?

//動(dòng)畫的執(zhí)行時(shí)間,需要注意的是一定要和animateTransition:里的動(dòng)畫時(shí)間保持一致
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()!
    
    //這個(gè)方法是iOS8之后才有的
    let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
    let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
    
    //iOS8之前取得的方式
    //        let view11 = vc1.view
    //        let view22 = vc2.view
    
    let 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 * dir
    
    var r2start = r2end
    r2start.origin.x += r2start.width * dir
    view2.frame = r2start
    containerView.addSubview(view2)
    
    //避免其他手勢(shì)事件的干擾
    UIApplication.sharedApplication().beginIgnoringInteractionEvents()
    //執(zhí)行具體動(dòng)畫丑慎,不同效果的代碼不同處就是在這了
    UIView.animateWithDuration(0.4, animations: {
        view1.frame = r1end
        view2.frame = r2end
    }) { (_) in
        //防止取消狀態(tài)
        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 ()<UITabBarControllerDelegate>

@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 <UIViewControllerAnimatedTransitioning> )tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
CusomTransitionAnimation *animation = [[CusomTransitionAnimation alloc]init];
animation.tabBarController = self.tabBarController;
return  animation;
}

- (CusomTransitionAnimation *)customAnimation
{
if (!_customAnimation) {
    _customAnimation = [[CusomTransitionAnimation alloc]init];
    _customAnimation.tabBarController = self.tabBarController;
}
return _customAnimation;
}

@end

動(dòng)畫類

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

@interface CusomTransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@property(nonatomic,strong) UITabBarController *tabBarController;

@end

//.m
#import "CusomTransitionAnimation.h"

@implementation CusomTransitionAnimation

- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.4;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)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實(shí)現(xiàn)是類似的喜喂,只不過調(diào)用動(dòng)畫類的方式不同

viewController實(shí)現(xiàn)

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 用來區(qū)分是push還是pop操作
    if operation == .Push {
        return CustomPushTransitionAimation()
    }
    else if operation == .Pop{
        return CustomPopTransitionAimation()
    }
    return nil
}
}

Push動(dòng)畫

import UIKit

class CustomPushTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {

//動(dòng)畫的執(zhí)行時(shí)間,需要注意的是一定要和animateTransition:里的動(dòng)畫時(shí)間保持一致
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()!
    
    //這個(gè)方法是iOS8之后才有的
    let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
    let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
    
    //iOS8之前取得的方式
    //        let view11 = vc1.view
    //        let view22 = vc2.view

    view2.transform = CGAffineTransformMakeScale(0.1, 0.1)
    containerView.addSubview(view2)
    
    //避免其他手勢(shì)事件的干擾
    UIApplication.sharedApplication().beginIgnoringInteractionEvents()
    //執(zhí)行具體動(dòng)畫竿裂,不同效果的代碼不同處就是在這了
    UIView.animateWithDuration(0.4, animations: {
        view2.transform = CGAffineTransformIdentity
    }) { (_) in
        //防止取消狀態(tài)
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        UIApplication.sharedApplication().endIgnoringInteractionEvents()
    }
}

func animationEnded(transitionCompleted: Bool) {
    if transitionCompleted {
        print("Completed")
    }else{
        print("NO Completed")
    }
}

}

Pop動(dòng)畫

import UIKit

class CustomPushTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {

//動(dòng)畫的執(zhí)行時(shí)間玉吁,需要注意的是一定要和animateTransition:里的動(dòng)畫時(shí)間保持一致
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()!
    
    //這個(gè)方法是iOS8之后才有的
    let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
    let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
    
    //iOS8之前取得的方式
    //        let view11 = vc1.view
    //        let view22 = vc2.view

    view2.transform = CGAffineTransformMakeScale(0.1, 0.1)
    containerView.addSubview(view2)
    
    //避免其他手勢(shì)事件的干擾
    UIApplication.sharedApplication().beginIgnoringInteractionEvents()
    //執(zhí)行具體動(dòng)畫,不同效果的代碼不同處就是在這了
    UIView.animateWithDuration(0.4, animations: {
        view2.transform = CGAffineTransformIdentity
    }) { (_) in
        //防止取消狀態(tài)
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        UIApplication.sharedApplication().endIgnoringInteractionEvents()
    }
}

func animationEnded(transitionCompleted: Bool) {
    if transitionCompleted {
        print("Completed")
    }else{
        print("NO Completed")
    }
}
}

可交互的自定義過渡動(dòng)畫

有兩種實(shí)現(xiàn)方式

使用百分比驅(qū)動(dòng)

使用這種方式比較簡(jiǎn)單铛绰,還是按照上述的方法先實(shí)現(xiàn)不可交互的自定義動(dòng)畫诈茧。接下來實(shí)現(xiàn)回調(diào)方法

func tabBarController(tabBarController: UITabBarController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

主要是UIPercentDrivenInteractiveTransition的使用,實(shí)質(zhì)是frozen animation捂掰。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

var rightEdgr : UIScreenEdgePanGestureRecognizer!
var leftEdgr : UIScreenEdgePanGestureRecognizer!
var inter : UIPercentDrivenInteractiveTransition!//這個(gè)類是關(guān)鍵
var interacting = false

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    let tab = (self.window!.rootViewController as! UITabBarController)
    tab.delegate = self
    
    let sep = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(pan))
    sep.edges = .Right
    sep.delegate = self
    tab.view.addGestureRecognizer(sep)
    self.rightEdgr = sep
    
    let sep2 = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(pan))
    sep2.edges = .Left
    sep2.delegate = self
    tab.view.addGestureRecognizer(sep2)
    self.leftEdgr = sep2
    
    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
    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 = false
    
    if gestureRecognizer == self.rightEdgr {
        result = (tab.selectedIndex < tab.viewControllers!.count - 1)
    }
    else{
        result = tab.selectedIndex > 0
    }
    return result
}

func pan(g : UIScreenEdgePanGestureRecognizer) -> Void {
    //重點(diǎn)需要處理的地方
    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()//調(diào)用該方法敢会,將快速結(jié)束之前所設(shè)置的操作
        }else{
            self.inter.cancelInteractiveTransition()//調(diào)用該方法,將恢復(fù)原狀
        }
        self.interacting = false
    case .Cancelled:
        self.inter.cancelInteractiveTransition()
        self.interacting = false
    default : break
    }
    
}
}



extension AppDelegate : UIViewControllerAnimatedTransitioning{

//動(dòng)畫的執(zhí)行時(shí)間这嚣,需要注意的是一定要和animateTransition:里的動(dòng)畫時(shí)間保持一致
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()!
    
    //這個(gè)方法是iOS8之后才有的
    let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
    let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
    
    //iOS8之前取得的方式
    //        let view11 = vc1.view
    //        let view22 = vc2.view
    
    
    let 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! UITabBarController
    
    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 * dir
    
    var r2start = r2end
    r2start.origin.x += r2start.width * dir
    view2.frame = r2start
    containerView.addSubview(view2)
    /*
    //避免其他手勢(shì)事件的干擾
    UIApplication.sharedApplication().beginIgnoringInteractionEvents()
    //執(zhí)行具體動(dòng)畫鸥昏,不同效果的代碼不同處就是在這了
    UIView.animateWithDuration(0.4, animations: {
        view1.frame = r1end
        view2.frame = r2end
    }) { (_) in
        //防止取消狀態(tài)
        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)
    
    }
}
不使用百分比驅(qū)動(dòng)

這種寫法雖然看起來會(huì)稍微復(fù)雜一些,但是他的可定制程度更高姐帚,因?yàn)槲覀兛梢灾苯訉?duì)fromView 和 toView等進(jìn)行直接操作吏垮。

不在需要具體實(shí)現(xiàn)animateTransition方法,但是他還是要存在的,因?yàn)樗膮f(xié)議必須實(shí)現(xiàn)的方法膳汪。

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    
}

將其中的代碼放到實(shí)現(xiàn)了協(xié)議UIViewControllerInteractiveTransitioning的類中實(shí)現(xiàn)唯蝶。放到方法

func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning){
}

中就可以了。

這次使用OC實(shí)現(xiàn)遗嗽,都熟悉下:

#import "AppDelegate.h"

@interface AppDelegate ()<UIViewControllerAnimatedTransitioning,UIViewControllerInteractiveTransitioning,UITabBarControllerDelegate,UIGestureRecognizerDelegate>

@property(nonatomic,strong) id<UIViewControllerContextTransitioning>transitionContext;
@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:{
        //可以在這里對(duì)fromView 和 toView 進(jìn)行直接操作粘我,所以,可定制程度高
        
        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中這個(gè)就很有用了征字,可以自己處理NavigationBar的效果等。
        [self.transitionContext updateInteractiveTransition:percent];
        break;
    }
    case UIGestureRecognizerStateEnded: {
        if (percent > 0.5) {
            //達(dá)到要求娇豫,設(shè)置成最終狀態(tài)
            [UIView animateWithDuration:0.2 animations:^{
                fromView.frame = self.r1end;
                toView.frame = toRectEnd;
            } completion:^(BOOL finished) {
                [self.transitionContext finishInteractiveTransition];
                [self.transitionContext completeTransition:YES];
            }];
        }
        else{
            //恢復(fù)原狀
            [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: {
        
        //恢復(fù)原狀
        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 <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                           interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController
{
return self.interacting ? self : nil;
}

- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
                 animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                                   toViewController:(UIViewController *)toVC
{
return self.interacting ? self : nil;
}

#pragma mark - UIViewControllerAnimatedTransitioning

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

//這個(gè)要實(shí)現(xiàn)匙姜,但是要保持為空
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{

}

#pragma mark - UIViewControllerInteractiveTransitioning
//這個(gè)其實(shí)就是要實(shí)現(xiàn) animateTransition: 原來實(shí)現(xiàn)的功能
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{

self.transitionContext = transitionContext;

//即最終要達(dá)到的狀態(tài)
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的過渡動(dòng)畫

實(shí)現(xiàn)主要要兩種方式

  • 不使用presentation controller
  • 使用presentation controller
不使用presentation controller

需要注意的地方是

  • 協(xié)議transitioningDelegate聲明的位置
  • 區(qū)分present 和 dismiss
  • 只有當(dāng)modalPresentationStyle *不是 .FullScreen *的時(shí)候,才能使用如下方式判定是present還是dismiss

上代碼

import UIKit

class ViewController2: UIViewController {

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    //這個(gè)不能再 viewDidLoad 里執(zhí)行冯痢,太晚了
    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<UITouch>, 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中氮昧,會(huì)取得presenting View,并將其添加到containerView中系羞,其他類型的則是郭计,containerView是在presenting view之前的,不能給containerView添加動(dòng)畫椒振,并且containerView不會(huì)移除。

使用Presentation Controller

在此之前梧乘,需要理解的一些東西

  • Animation Controller 用來負(fù)責(zé)動(dòng)畫效果澎迎,也就是presented View 如何移動(dòng)到最終目標(biāo)位置
  • Presentation Controller 確定presented View的最終位置,還有向containerView上添加一些視圖选调,例如半透明效果什么的夹供。
    舉個(gè)例子來說明,如果只是實(shí)現(xiàn)了UIPresentationController 而沒有做上面的那些工作仁堪,會(huì)使用默認(rèn)動(dòng)畫彈出哮洽。

接下來說說實(shí)現(xiàn):
在實(shí)現(xiàn)上面效果的基礎(chǔ)上,需要在做幾點(diǎn)工作

  • 在設(shè)置presented viewController的 transitioningDelegate屬性的同時(shí)弦聂,還需要設(shè)置其的modalPresentationStyle為.Custom鸟辅,這個(gè)是必須的。(注意代碼位置)

       // 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
      }
    
  • 在實(shí)現(xiàn)另一個(gè)方法

    @available(iOS 8.0, *)
    optional public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
    

接下來便是在 UIPresentationController的子類中重載他的方法來達(dá)到我們所要的效果

方法的說明:

// 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

使用下面方法來個(gè)添加的額外視圖進(jìn)行布局

containerViewWillLayoutSubviews
containerViewDidLayoutSubviews

方法

shouldPresentInFullscreen

默認(rèn)返回的是true莺葫,如果返回false匪凉,會(huì)將presentation改變?yōu)?.CurrentContext

主要就這幾個(gè),完結(jié)捺檬。

Transition Coordinator的使用

主要注意幾個(gè)方法的使用就可以了

  • animateAlongsideTransition:completion:
    可以用來給已經(jīng)在視圖上的子視圖添加動(dòng)畫效果

    - (void)viewWillAppear:(BOOL)animated
    {
    [super viewWillAppear:animated];
    if (self.transitionCoordinator != nil) {
      [self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
          NSLog(@"Transition Animation");
          
          self.animationView.center = self.view.center;
          
      } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
          NSLog(@"Transition completion");
      }];
    }
    else{
      NSLog(@"Transition is NULL");
    }
    }
    
  • notifyWhenInteractionEndsUsingBlock:

監(jiān)控視圖返回時(shí)手勢(shì)動(dòng)作是否成功再层。比如在UINavigationController中左側(cè)滑動(dòng)返回時(shí),需要在上一個(gè)ViewController中實(shí)現(xiàn)。

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.transitionCoordinator != nil) {
    
    [self.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        if ([context isCancelled]) {
            NSLog(@"1 Interaction Cancelled");
            return ;
        }
        NSLog(@"1 Interaction Ends");
    }];
}
else{
    NSLog(@"1 Transition is NULL");
}
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末聂受,一起剝皮案震驚了整個(gè)濱河市蒿秦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛋济,老刑警劉巖渤早,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瘫俊,居然都是意外死亡鹊杖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門扛芽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骂蓖,“玉大人,你說我怎么就攤上這事川尖〉窍拢” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵叮喳,是天一觀的道長(zhǎng)被芳。 經(jīng)常有香客問我,道長(zhǎng)馍悟,這世上最難降的妖魔是什么畔濒? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮锣咒,結(jié)果婚禮上侵状,老公的妹妹穿的比我還像新娘。我一直安慰自己毅整,他們只是感情好趣兄,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悼嫉,像睡著了一般艇潭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上戏蔑,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天蹋凝,我揣著相機(jī)與錄音,去河邊找鬼辛臊。 笑死仙粱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的彻舰。 我是一名探鬼主播伐割,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼候味,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了隔心?” 一聲冷哼從身側(cè)響起白群,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硬霍,沒想到半個(gè)月后帜慢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唯卖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年粱玲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拜轨。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抽减,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出橄碾,到底是詐尸還是另有隱情卵沉,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布法牲,位于F島的核電站史汗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拒垃。R本人自食惡果不足惜停撞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恶复。 院中可真熱鬧怜森,春花似錦、人聲如沸谤牡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翅萤。三九已至,卻和暖如春腊满,著一層夾襖步出監(jiān)牢的瞬間套么,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工碳蛋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胚泌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓肃弟,卻偏偏與公主長(zhǎng)得像玷室,于是被迫代替她去往敵國(guó)和親零蓉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

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