// Copyright © 2020 Metabolist. All rights reserved. import UIKit protocol ZoomAnimatorDelegate: AnyObject { func transitionWillStartWith(zoomAnimator: ZoomAnimator) func transitionDidEndWith(zoomAnimator: ZoomAnimator) func referenceView(for zoomAnimator: ZoomAnimator) -> UIView? func referenceViewFrameInTransitioningView(for zoomAnimator: ZoomAnimator) -> CGRect? } final class ZoomAnimator: NSObject { weak var fromDelegate: ZoomAnimatorDelegate? weak var toDelegate: ZoomAnimatorDelegate? var transitionView: UIView? var isPresenting = true } extension ZoomAnimator: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { isPresenting ? .defaultAnimationDuration : .shortAnimationDuration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { fromDelegate?.transitionWillStartWith(zoomAnimator: self) toDelegate?.transitionWillStartWith(zoomAnimator: self) if isPresenting { animateZoomInTransition(using: transitionContext) } else { animateZoomOutTransition(using: transitionContext) } } } private extension ZoomAnimator { private func animateZoomInTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toVC = transitionContext.viewController(forKey: .to), let fromVC = transitionContext.viewController(forKey: .from), let fromReferenceView = fromDelegate?.referenceView(for: self), let toReferenceView = toDelegate?.referenceView(for: self), let fromReferenceViewFrame = fromDelegate?.referenceViewFrameInTransitioningView(for: self) else { return } toVC.view.alpha = 0 toVC.view.frame = transitionContext.finalFrame(for: toVC) toReferenceView.isHidden = true transitionContext.containerView.addSubview(toVC.view) if transitionView == nil, let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() { transitionView.frame = fromReferenceViewFrame transitionView.layer.contentsRect = fromReferenceView.layer.contentsRect transitionView.layer.cornerRadius = fromReferenceView.layer.cornerRadius self.transitionView = transitionView transitionContext.containerView.addSubview(transitionView) } fromReferenceView.isHidden = true let referenceViewFrame = (fromReferenceView as? ZoomAnimatableView)?.frame(inView: toVC.view) ?? .zero let finalTransitionFrame = referenceViewFrame.containsNaN ? .zero : referenceViewFrame UIView.animate( withDuration: transitionDuration(using: transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: [.transitionCrossDissolve]) { self.transitionView?.frame = finalTransitionFrame self.transitionView?.layer.contentsRect = .defaultContentsRect self.transitionView?.layer.cornerRadius = 0 toVC.view.alpha = 1.0 fromVC.tabBarController?.tabBar.alpha = 0 } completion: { _ in self.transitionView?.removeFromSuperview() toReferenceView.isHidden = false fromReferenceView.isHidden = false self.transitionView = nil transitionContext.completeTransition(!transitionContext.transitionWasCancelled) self.toDelegate?.transitionDidEndWith(zoomAnimator: self) self.fromDelegate?.transitionDidEndWith(zoomAnimator: self) } } private func animateZoomOutTransition(using transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView guard let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), let fromReferenceView = fromDelegate?.referenceView(for: self), let fromReferenceViewFrame = fromDelegate?.referenceViewFrameInTransitioningView(for: self) else { return } let toReferenceView = toDelegate?.referenceView(for: self) let toReferenceViewFrame = toDelegate?.referenceViewFrameInTransitioningView(for: self) toReferenceView?.isHidden = true if transitionView == nil, let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() { transitionView.frame = fromReferenceViewFrame self.transitionView = transitionView containerView.addSubview(transitionView) } containerView.insertSubview(toVC.view, belowSubview: fromVC.view) fromReferenceView.isHidden = true UIView.animate( withDuration: transitionDuration(using: transitionContext)) { fromVC.view.alpha = 0 if let toReferenceViewFrame = toReferenceViewFrame { self.transitionView?.frame = toReferenceViewFrame } else { self.transitionView?.alpha = 0 } self.transitionView?.layer.contentsRect = toReferenceView?.layer.contentsRect ?? .defaultContentsRect self.transitionView?.layer.cornerRadius = toReferenceView?.layer.cornerRadius ?? 0 toVC.tabBarController?.tabBar.alpha = 1 } completion: { _ in self.transitionView?.removeFromSuperview() toReferenceView?.isHidden = false fromReferenceView.isHidden = false transitionContext.completeTransition(!transitionContext.transitionWasCancelled) self.toDelegate?.transitionDidEndWith(zoomAnimator: self) self.fromDelegate?.transitionDidEndWith(zoomAnimator: self) } } }