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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值