123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- import UIKit
- internal protocol AnimationModel {
-
- var animationOptions: UIView.AnimationOptions { get }
-
- var completeGestureDuration: Double { get }
-
- var dismissDuration: Double { get }
-
- var initialSpringVelocity: CGFloat { get }
-
- var presentDuration: Double { get }
-
- var usingSpringWithDamping: CGFloat { get }
- }
- internal protocol SideMenuAnimationControllerDelegate: class {
- func sideMenuAnimationController(_ animationController: SideMenuAnimationController, didDismiss viewController: UIViewController)
- func sideMenuAnimationController(_ animationController: SideMenuAnimationController, didPresent viewController: UIViewController)
- }
- internal final class SideMenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
- typealias Model = AnimationModel & PresentationModel
- private var config: Model
- private weak var containerView: UIView?
- private let leftSide: Bool
- private weak var originalSuperview: UIView?
- private var presentationController: SideMenuPresentationController?
- private unowned var presentedViewController: UIViewController?
- private unowned var presentingViewController: UIViewController?
- weak var delegate: SideMenuAnimationControllerDelegate?
- init(config: Model, leftSide: Bool, delegate: SideMenuAnimationControllerDelegate? = nil) {
- self.config = config
- self.leftSide = leftSide
- self.delegate = delegate
- }
- func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
- guard
- let presentedViewController = transitionContext.presentedViewController,
- let presentingViewController = transitionContext.presentingViewController
- else { return }
- if transitionContext.isPresenting {
- self.containerView = transitionContext.containerView
- self.presentedViewController = presentedViewController
- self.presentingViewController = presentingViewController
- self.presentationController = SideMenuPresentationController(
- config: config,
- leftSide: leftSide,
- presentedViewController: presentedViewController,
- presentingViewController: presentingViewController,
- containerView: transitionContext.containerView
- )
- }
- transition(using: transitionContext)
- }
- func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
- guard let transitionContext = transitionContext else { return 0 }
- return duration(presenting: transitionContext.isPresenting, interactive: transitionContext.isInteractive)
- }
- func animationEnded(_ transitionCompleted: Bool) {
- guard let presentedViewController = presentedViewController else { return }
- if presentedViewController.isHidden {
- delegate?.sideMenuAnimationController(self, didDismiss: presentedViewController)
- } else {
- delegate?.sideMenuAnimationController(self, didPresent: presentedViewController)
- }
- }
- func transition(presenting: Bool, animated: Bool = true, interactive: Bool = false, alongsideTransition: (() -> Void)? = nil, complete: Bool = true, completion: ((Bool) -> Void)? = nil) {
- prepare(presenting: presenting)
- transitionWillBegin(presenting: presenting)
- transition(
- presenting: presenting,
- animated: animated,
- interactive: interactive,
- animations: { [weak self] in
- guard let self = self else { return }
- self.transition(presenting: presenting)
- alongsideTransition?()
- }, completion: { [weak self] _ in
- guard let self = self else { return }
- if complete {
- self.transitionDidEnd(presenting: presenting, completed: true)
- self.finish(presenting: presenting, completed: true)
- }
- completion?(true)
- })
- }
- func layout() {
- presentationController?.containerViewWillLayoutSubviews()
- }
- }
- private extension SideMenuAnimationController {
- func duration(presenting: Bool, interactive: Bool) -> Double {
- if interactive { return config.completeGestureDuration }
- return presenting ? config.presentDuration : config.dismissDuration
- }
- func prepare(presenting: Bool) {
- guard
- presenting,
- let presentingViewController = presentingViewController,
- let presentedViewController = presentedViewController
- else { return }
- originalSuperview = presentingViewController.view.superview
- containerView?.addSubview(presentingViewController.view)
- containerView?.addSubview(presentedViewController.view)
- }
- func transitionWillBegin(presenting: Bool) {
-
- containerView?.isUserInteractionEnabled = false
- if presenting {
- presentationController?.presentationTransitionWillBegin()
- } else {
- presentationController?.dismissalTransitionWillBegin()
- }
- }
- func transition(presenting: Bool) {
- if presenting {
- presentationController?.presentationTransition()
- } else {
- presentationController?.dismissalTransition()
- }
- }
- func transitionDidEnd(presenting: Bool, completed: Bool) {
- if presenting {
- presentationController?.presentationTransitionDidEnd(completed)
- } else {
- presentationController?.dismissalTransitionDidEnd(completed)
- }
- containerView?.isUserInteractionEnabled = true
- }
- func finish(presenting: Bool, completed: Bool) {
- guard
- presenting != completed,
- let presentingViewController = self.presentingViewController
- else { return }
- presentedViewController?.view.removeFromSuperview()
- originalSuperview?.addSubview(presentingViewController.view)
- }
- func transition(using transitionContext: UIViewControllerContextTransitioning) {
- prepare(presenting: transitionContext.isPresenting)
- transitionWillBegin(presenting: transitionContext.isPresenting)
- transition(
- presenting: transitionContext.isPresenting,
- animated: transitionContext.isAnimated,
- interactive: transitionContext.isInteractive,
- animations: { [weak self] in
- guard let self = self else { return }
- self.transition(presenting: transitionContext.isPresenting)
- }, completion: { [weak self] _ in
- guard let self = self else { return }
- let completed = !transitionContext.transitionWasCancelled
- self.transitionDidEnd(presenting: transitionContext.isPresenting, completed: completed)
- self.finish(presenting: transitionContext.isPresenting, completed: completed)
-
- transitionContext.completeTransition(completed)
- })
- }
- func transition(presenting: Bool, animated: Bool = true, interactive: Bool = false, animations: @escaping (() -> Void) = {}, completion: @escaping ((Bool) -> Void) = { _ in }) {
- if !animated {
- animations()
- completion(true)
- return
- }
- let duration = self.duration(presenting: presenting, interactive: interactive)
- if interactive {
-
- UIView.animate(
- withDuration: duration,
- delay: duration,
- options: .curveLinear,
- animations: animations,
- completion: completion
- )
- return
- }
- UIView.animate(
- withDuration: duration,
- delay: 0,
- usingSpringWithDamping: config.usingSpringWithDamping,
- initialSpringVelocity: config.initialSpringVelocity,
- options: config.animationOptions,
- animations: animations,
- completion: completion
- )
- }
- }
- private extension UIViewControllerContextTransitioning {
- var isPresenting: Bool {
- return viewController(forKey: .from)?.presentedViewController === viewController(forKey: .to)
- }
- var presentingViewController: UIViewController? {
- return viewController(forKey: isPresenting ? .from : .to)
- }
- var presentedViewController: UIViewController? {
- return viewController(forKey: isPresenting ? .to : .from)
- }
- }
|