自定义modal转场动画,滑动手势控制 dismiss 过程

效果:

假设有:
1.两个视图控制器:presentingVC, presentedVC
2.一个继承于UIPercentDrivenInteractiveTransition,并遵守协议UIViewControllerAnimatedTransitioning的实例:transitionAnimator
3.presentingVC.present(presentedVC, animated: true, completion: nil)

一. 自定义modal转场动画
  1. 要自定义dismiss转场动画,presentedVC视图控制器就要遵守UIViewControllerTransitioningDelegate协议(不要忘记设置 presentedVC.transitioningDelegate = self),并且实现以下两个方法:
1) 提供present动画
//这个方法在调用presentingVC.present(presentedVC, animated: true, completion: nil)时,被调用
// 返回transitionAnimator,正是由这个实例提供自定义转场的
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?{
  return transitionAnimator
}

2) 提供dismiss动画
// 这个方法在调用presentedVC.dismiss(animated: true, completion: nil)时,被调用
// 返回实例 transitionAnimator
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?{
  return transitionAnimator
}
  1. TransitionAnimator遵守UIViewControllerAnimatedTransitioning协议,并必须实现以下两个协议方法:
 //返回转场动画的持续时间
 1) func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval 

//实现转场动画
 2) func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
二. 滑动手势控制 dismiss 过程

1) 根据滑动手势的偏移量来设置dismiss动画的完成百分比,再让presentedVC视图控制器实现UIViewControllerTransitioningDelegate协议的另一个方法:


func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning)
        -> UIViewControllerInteractiveTransitioning?{
  return transitionAnimator
}
  1. 根据滑动手势的偏移量,设置dismiss 动画的完成百分比
   transitionAnimator.transitionAnimator.update(precent)
二. 关键代码

presentedVC

import UIKit

protocol DetialViewControllerDelegate {
    func detialViewController(_ controller: DetialViewController, scrollViewPanGestureRecognizerDidChange:UIPanGestureRecognizer)
}
struct PanningData {
    let contentOffset: CGPoint
    let isDragging: Bool
    let translation: CGPoint
    let velocity: CGPoint
}
/// 转场状态
enum TransitionState: String{
    case none
    case started
    case updating
    case canceling
    case finishing
    fileprivate var active: Bool {
        return self == .started || self == .updating
    }
}
class DetialViewController: UITableViewController{
    var delegate: DetialViewControllerDelegate?
    var transitionAnimator = TransitionAnimator()
    var transitionPhase : TransitionState = .none


    override func viewDidLoad() {
        super.viewDidLoad()
        self.transitioningDelegate = self
        tableView.delegate = self
        tableView.dataSource = self
       // tableView.bounces = false
       tableView.panGestureRecognizer.addTarget(self, action: #selector(detailViewController(_:)))
    }

}

extension DetialViewController:UIViewControllerTransitioningDelegate{
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        var cell:UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "sss")
        if (cell == nil) {
            cell = UITableViewCell(style: .default, reuseIdentifier: "sss")
        }
        cell?.textLabel?.text = String(indexPath.row)
        return cell!
    }

    @objc func detailViewController(_ recognizer: UIPanGestureRecognizer){
        guard let scrollView =  recognizer.view as? UIScrollView else { return }

        let data = PanningData(contentOffset: scrollView.contentOffset, isDragging: scrollView.isTracking, translation: recognizer.translation(in: scrollView), velocity: recognizer.velocity(in: scrollView))

        if data.contentOffset.y > 0 {
            transitionPhase = transitionPhase == .none ? .none : .canceling
        }else if data.isDragging && data.translation.y > 0 && !transitionPhase.active {
            transitionPhase = .started
        }
        else if data.isDragging && data.translation.y > 0 && transitionPhase.active {
            transitionPhase = .updating
        }
        else if data.isDragging && data.translation.y < 0 && transitionPhase.active {
            transitionPhase = .canceling
        }
        else if !data.isDragging && data.translation.y > 0 && transitionPhase.active {
            transitionPhase = data.velocity.y > 0 ? .finishing : .canceling
        }

        if transitionPhase == .started || transitionPhase == .updating{
            self.transitionAnimator.isInFlight = true
        }else {
            self.transitionAnimator.isInFlight = false
        }


        print(transitionPhase.rawValue)

        if transitionPhase == .started {
            scrollView.bounces = false
            self.dismiss(animated: true, completion: nil)
        }

        if transitionPhase == .updating || transitionPhase == .started {
            let precent = data.translation.y / self.view.bounds.height
            self.transitionAnimator.update(precent)
        }
        if transitionPhase == .canceling {
            scrollView.bounces = true
            self.transitionAnimator.cancel()
        }

        if transitionPhase == .finishing {
            self.transitionAnimator.finish()
        }

    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return self.transitionAnimator
    }

    func animationController(forPresented presented: UIViewController,presenting: UIViewController,source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self.transitionAnimator
    }

    ///这个协议用来返回控制 dismiss 完成度的类UIPercentDrivenInteractiveTransition,此类遵守 UIViewControllerInteractiveTransitioning
    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning)
        -> UIViewControllerInteractiveTransitioning? {
            return self.transitionAnimator.isInFlight ? self.transitionAnimator : nil
    }
}

TransitionAnimator

import UIKit
class TransitionAnimator: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning{
    private let dismissedOverlayColor = UIColor(white: 1, alpha: 0)
    private let presentedOverlayColor = UIColor(white: 1, alpha: 1)
    fileprivate let darkOverlayView = UIView()
    internal var isInFlight = true

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return transitionContext?.isInteractive == .some(true) ? 0.6:0.4
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
              let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        else{ return }

        let containerView = transitionContext.containerView

        if toVC.isBeingPresented {
            self.animatePresentation(fromViewController: fromVC,
                                     toViewController: toVC,
                                     containerView: containerView,
                                     transitionContext: transitionContext)
        }else{
            self.animateDismissal(fromViewController: fromVC,
                                  toViewController: toVC,
                                  containerView: containerView,
                                  transitionContext: transitionContext)

        }

    }


    fileprivate func animatePresentation(
        fromViewController fromVC: UIViewController,
        toViewController toVC: UIViewController,
        containerView: UIView,
        transitionContext: UIViewControllerContextTransitioning) {

        containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
        containerView.insertSubview(self.darkOverlayView, belowSubview: toVC.view)

        self.darkOverlayView.frame = containerView.bounds
        self.darkOverlayView.backgroundColor = dismissedOverlayColor

        let bottomLeftCorner = CGPoint(x: 0, y: containerView.bounds.height)
        let finalFrame = CGRect(origin: .zero, size: containerView.bounds.size)

        toVC.view.frame = CGRect(origin: bottomLeftCorner, size: containerView.bounds.size)

        UIView.animate(
            withDuration: self.transitionDuration(using: transitionContext),
            delay: 0,
            options: [.curveEaseOut],
            animations: {
                toVC.view.frame = finalFrame
                self.darkOverlayView.backgroundColor = self.presentedOverlayColor
        },
            completion: { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
        )
    }
    /// toViewController 表示将要出现的VC
    /// fromViewController 表示将要dismiss的VC
    fileprivate func animateDismissal(fromViewController fromVC: UIViewController,
                                      toViewController toVC: UIViewController,
                                      containerView: UIView,
                                      transitionContext: UIViewControllerContextTransitioning) {

        containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
        containerView.insertSubview(self.darkOverlayView, belowSubview: fromVC.view)

        self.darkOverlayView.frame = containerView.bounds
        self.darkOverlayView.backgroundColor = presentedOverlayColor

        let bottomLeftCorner = CGPoint(x: 0, y: containerView.bounds.height)
        let finalFrame = CGRect(origin: bottomLeftCorner, size: containerView.bounds.size)
        toVC.view.frame = containerView.bounds

        let animationCurve: UIViewAnimationOptions = transitionContext.isInteractive == .some(true)
            ? .curveLinear
            : .curveEaseOut
        UIView.animate(
            withDuration: self.transitionDuration(using: transitionContext),
            delay: 0,
            options: [animationCurve],
            animations: {
                fromVC.view.frame = finalFrame
                if transitionContext.isInteractive {
                    fromVC.view.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
                }
                self.darkOverlayView.backgroundColor = self.dismissedOverlayColor
        },
            completion: { _ in
                self.darkOverlayView.backgroundColor = transitionContext.transitionWasCancelled
                    ? .black
                    : self.darkOverlayView.backgroundColor
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
        )
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值