Swift ViewControllerTransitions

自定義Modally轉場動畫

準備

delegate轉交給viewcontroller而不是系統

vc.transitioningDelegate = self

遵循協議

extension MainViewController:UIViewControllerTransitioningDelegate {
    //非交互動畫
    
    //出現的函數,返回一個UIViewControllerAnimatedTransitioning的一個動畫器,於是就新建一個
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentAnimator()
    }
    //退出的函數,同上返回一個動畫器
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissAnimator()
    }
    
    //交互動畫
    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        //返回UIViewControllerInteractiveTransitioning的子類,完成交互動畫
        return panInteraction.isInteractive ? panInteraction : nil 
    }
}

兩個非交互動畫的動畫器

創建動畫器

在這裏插入圖片描述

遵循兩個協議

完成兩個固定方法,一個設置動畫經過時間,一個設置動畫邏輯

class PresentAnimator:NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {}
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {}
}

完成動畫器

PresentAnimator:

class PresentAnimator:NSObject, UIViewControllerAnimatedTransitioning {
    
    //動畫的經過時間
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }
    
    //動畫邏輯
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
        guard
            //轉場開始的vc
//            let fromVC = transitionContext.viewController(forKey: .from),
            //轉場開始vc的根視圖
            let fromView = transitionContext.view(forKey: .from),
            
            //轉場結束的vc
//            let toVC = transitionContext.viewController(forKey: .to),
            //轉場結束的vc的根視圖
            let toView = transitionContext.view(forKey: .to)
        else{return}
        
        //類似於NavigationView
        let containerView = transitionContext.containerView
        //加入toView,UIKit會自動加入fromView
        containerView.addSubview(toView)
        
        toView.alpha = 0
        toView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
        
        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            animations: {
                fromView.alpha = 0
                fromView.transform = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
                toView.alpha = 1
                toView.transform = .identity
        }) { _ in
            fromView.transform = .identity
            toView.transform = .identity
            
            //UIKit調用這個方法來結束轉場動畫
            transitionContext.completeTransition(true)
        }
        
    }
    
//tcontainerView在動畫結束的時候會自動移除fromView只剩下toView,一旦toView被dismiss就會黑屏,但是fromView的transform屬性還在,所以在最後要給他還原
}


DismissAnimator:

class DismissAnimator: NSObject,UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
        guard
            let fromView = transitionContext.view(forKey: .from),
            let toView = transitionContext.view(forKey: .to)
        else{return}
        
        let containerView = transitionContext.containerView
        
        containerView.addSubview(toView)
        
        toView.alpha = 0
        toView.transform = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
        
        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            animations: {
                
                toView.alpha = 1
                toView.transform = .identity
                
                fromView.alpha = 0
                fromView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
        }) { _ in
            fromView.transform = .identity
            toView.transform = .identity
            
            transitionContext.completeTransition(true)
        }
        
    }
    

}


在VC上設置dismiss

在ImageView上添加手勢記得要加上

在這裏插入圖片描述

如果imageView是代碼寫上去的話添加

detailImageView.isUserInteractionEnabled = true
class DetailViewController: UIViewController {

    
    @IBOutlet weak var detailImageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //target表示這個手勢在哪裏運行,actiont傳入函數的固定寫法
        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(tap:)))
        detailImageView.addGestureRecognizer(tap)
        // Do any additional setup after loading the view.
    }
    

    @objc func handleTap(tap:UITapGestureRecognizer) {
        dismiss(animated: true, completion: nil)
    }

}

交互動畫的動畫器

創建動畫器

是UIViewControllerInteractiveTransitioning的子類,用於完成交互動畫
在這裏插入圖片描述

添加交互動畫的flag

vc.transitioningDelegate = self
因爲當執行玩這個操作的時候,會先去尋找交互動畫,若交互動畫返回值不是nil就一直執行交互動畫而不執行非交互動畫,所以要給交互動畫設置一個flag(isInteractive)設定交互動畫開關的條件

返回UIViewControllerInteractiveTransitioning的子類,完成交互動畫

func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return panInteraction.isInteractive ? panInteraction : nil  
}

完成交互動畫

class PanInteraction: UIPercentDrivenInteractiveTransition {
    
    var isInteractive = false
    
    var detailVC = DetailViewController()
    
    init(detailVC: DetailViewController) {
        self.detailVC = detailVC//1.先給本類的所有存儲屬性賦值
        super.init()//2.再給所有父類的存儲屬性賦值(原本是系統默認給我們加的,但是是在最後面)
        let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
        detailVC.view.addGestureRecognizer(pan)
    }
    
    @objc func handlePan(pan: UIPanGestureRecognizer) {
        let progress = pan.translation(in: pan.view).x / 200
        
        switch pan.state {
        case .began:
            isInteractive = true
            detailVC.dismiss(animated: true, completion: nil)
        case .changed:
            update(progress)
        case .ended, .cancelled:
            isInteractive = false
            if progress > 0.5 {
                finish()
            }else{
                cancel()
            }
        default: break
        }
    }
}

在增加了交互動畫之後,結束轉場動畫就可能在交互動畫過程中取消
就得在兩個非交互動畫下面更改transitionContext.completeTransition
讓UIKit調用這個方法來結束轉場動畫

transitionContext.completeTransition(!transitionContext.transitionWasCancelled)

自定義Navigation轉場動畫

準備

delegate轉交給navigationController而不是系統

override func viewDidLoad() {
    super.viewDidLoad()
    
    navigationController?.delegate = self
}

遵循協議,完成兩個函數


extension MainViewController: UINavigationControllerDelegate{
//非交互動畫
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        switch operation {
        case .push:
            return PushAnimator()
        case .pop:
            return PopAnimator()
        default:
            return nil
        }
    }
    
//交互動畫
    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        //判斷panInteraction是不是nil就是判斷用戶有沒有到達detailVC-若到達才加pop交互動畫
        //用戶pop完後,isInteractive變爲false,這裏仍舊返回nil,不影響下一次的push操作
        guard let panInteraction = panInteraction ,panInteraction.isInteractive else{return nil}
        return panInteraction
    }
}

完成兩個非交互動畫的動畫器

push動畫器內容同modally
pop動畫器模仿系統的有陰影,且toView保留1/3

class PopAnimate: NSObject,UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        0.3
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        
        guard
            let fromView = transitionContext.view(forKey: .from),
            let toView = transitionContext.view(forKey: .to)
        else{return}
        
//        containerView.addSubview(toView)
        //把toView放在fromView的下面
        containerView.insertSubview(toView, belowSubview: fromView)
        
        toView.alpha = 1
        toView.transform = CGAffineTransform(translationX: -containerView.frame.width / 3, y: 0)
        
        fromView.layer.shadowOpacity = 0.5//即陰影的alpha
        fromView.layer.shadowRadius = 10//陰影大小
        //每次形成陰影前都會系統都會計算陰影的大小,非常消耗內存,添加shadowPath提前告知陰影的大小
        //bounds相對於自己的位置和大小 frame是相對於複式圖的位置和大小
        fromView.layer.shadowPath = UIBezierPath(rect: fromView.bounds).cgPath
        
        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            animations: {
                fromView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
                toView.transform = .identity
        }) { _ in
            toView.transform = .identity
            fromView.transform = .identity
            
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

完成交互動畫的動畫器

實例化動畫器

因爲push不會有交互動畫,所以在push之後實例化動畫器,這樣也不會影響push非交互動畫

class MainViewController: UIViewController {

    var panInteraction: PanInteraction?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        navigationController?.delegate = self
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let detailVC = segue.destination as? DetailViewController{
            //panInteraction在第一次push轉場前一直是nil,不影響push的非交互動畫
            //轉場後纔給panInteraction賦值
            panInteraction = PanInteraction(detailVC: detailVC)
        }
    }
}

完成交互動畫

基本同Modally

class PanInteraction: UIPercentDrivenInteractiveTransition {
    
    let detailVC: DetailViewController
    var isInteractive = false

    init(detailVC: DetailViewController){
        self.detailVC = detailVC
        super.init()
        
        let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
        detailVC.view.addGestureRecognizer(pan)
    }
    
    
    @objc func handlePan(pan: UIPanGestureRecognizer){
        
        let progress = pan.translation(in: pan.view).x / 200
        
        switch pan.state {
        case .began:
            isInteractive = true
            detailVC.navigationController?.popViewController(animated: true)
        case .changed:
            update(progress)
        case .cancelled , .ended:
            isInteractive = false
            if progress > 0.5{
                finish()
            }else{
                cancel()
            }
        default:
            break
        }
    }
}

自定義TabBarController轉場動畫

準備

添加一個UITabBarController

在這裏插入圖片描述
在這裏插入圖片描述

設置delegate

class TabBarController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        // Do any additional setup after loading the view.
    }
}

遵循協議

這裏的非交互動畫設置了一個enum用一個動畫器完成兩個動畫

enum Operation {
    case toRight, toLeft
}
extension TabBarController: UITabBarControllerDelegate {
    
    //非交互動畫
    func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        let fromIndex = tabBarController.viewControllers!.firstIndex(of: fromVC)!
        let toIndex = tabBarController.viewControllers!.firstIndex(of: toVC)!
        let operation: Operation = toIndex > fromIndex ? .toRight : .toLeft
        
        return CustomAnimator(operation: operation)

        
        
    }
    
    //交互動畫
    func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return customInteracton.isInteractive ? customInteracton : nil
    }
}

完成非交互動畫動畫器

class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning{
    let operation:Operation
    var flag = 1
    
    init(operation:Operation) {
        self.operation = operation
        super.init()
    }
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
        let containerView = transitionContext.containerView
        guard
            let fromView = transitionContext.view(forKey: .from),
            let toView = transitionContext.view(forKey: .to)
        else{return}
        
        containerView.addSubview(toView)
        
        if operation == .toRight {
            flag = 1
        }else{
            flag = -1
        }
        
        toView.alpha = 0
        toView.transform = CGAffineTransform(translationX: containerView.frame.width*CGFloat(flag), y: 0)
        
        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            animations: {
                
                fromView.alpha = 0
                fromView.transform = CGAffineTransform(translationX: -containerView.frame.width*CGFloat(self.flag), y: 0)
                
                toView.alpha = 1
                toView.transform = .identity
        }) { _ in
            
            toView.transform = .identity
            fromView.transform = .identity
            
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

完成交互動畫動畫器

實例化動畫器

class TabBarController: UITabBarController {
    
    var customInteracton:CustomInteraction!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        customInteracton = CustomInteraction(tabBarVC: self)
        self.delegate = self
        // Do any additional setup after loading the view.
    }
}

完成動畫器

通過改變tabBarVC.selectedIndex可以改變當前頁面
左右滑動translationX正負不同,由此可以判斷左右滑動

class CustomInteraction: UIPercentDrivenInteractiveTransition {
    
    let tabBarVC: TabBarController
    var isInteractive = false
    
    init(tabBarVC:TabBarController) {
        self.tabBarVC = tabBarVC
        super.init()
        
        let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
        tabBarVC.view.addGestureRecognizer(pan)
        
    }
    
    @objc func handlePan(pan: UIPanGestureRecognizer) {
        let translationX = pan.translation(in: pan.view).x
        //左右滑動translationX正負不同
        let progress = abs(translationX) / 200
        switch pan.state {
        case .began:
            isInteractive = true
            if translationX < 0 {
                if tabBarVC.selectedIndex < tabBarVC.viewControllers!.count - 1{
                    //通過改變tabBarVC.selectedIndex可以改變當前頁面
                    tabBarVC.selectedIndex += 1
                }
            }else{
                if tabBarVC.selectedIndex > 0 {
                    tabBarVC.selectedIndex -= 1
                }
            }
        case .changed:
            update(progress)
        case.ended,.cancelled:
            isInteractive = false
            if progress > 0.5 {
                finish()
            }else{
                cancel()
            }
        default:
            break
        }
    }
}

第三方包

https://github.com/HeroTransitions/Hero

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章