viveksinghmehta
1/9/2019 - 9:53 AM

Apple music like model presentation animation.


import UIKit

extension UIDevice {
    
    fileprivate static var isIPhoneX: Bool {
        if UIDevice().userInterfaceIdiom == .phone {
            switch UIScreen.main.nativeBounds.height {
            case 2436.0, 2688.0:
                return true
            default:
                return false
            }
        }
        return false
    }
}

public final class KagomeTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
    
    // MARK: - KagomeTransitioningDelegate
    
    public var shouldCoverSourceController: Bool = false
    public var cornerRadius: CGFloat = 10.0
    
    // MARK: - UIViewControllerTransitioningDelegate
    
    public func animationController(forPresented presented: UIViewController,
                                    presenting: UIViewController,
                                    source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return KagomeTransitionController(isPresenting: true,
                                          shouldCover: shouldCoverSourceController,
                                          cornerRadius: cornerRadius)
    }
    
    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        var radius: CGFloat = 0.0
        if dismissed.presentingViewController != nil && dismissed.presentingViewController?.transitioningDelegate is KagomeTransitioningDelegate {
            radius = self.cornerRadius
        }
        return KagomeTransitionController(isPresenting: false,
                                          shouldCover: shouldCoverSourceController,
                                          cornerRadius: radius)
    }
}

public final class KagomeTransitionController: NSObject, UIViewControllerAnimatedTransitioning {
    
    // MARK: - KagomeTransitionController
    
    private let isPresenting: Bool
    private let shouldCover: Bool
    private let cornerRadius: CGFloat
    
    public init(isPresenting presenting: Bool,
                shouldCover: Bool,
                cornerRadius: CGFloat = 10.0) {
        self.isPresenting = presenting
        self.shouldCover = shouldCover
        self.cornerRadius = cornerRadius
        super.init()
    }
    
    private func rearViewTranslation(isFirst: Bool) -> CGFloat {
        if isFirst {
            if UIDevice.isIPhoneX {
                return 4.0
            }
            return -8.0
        } else {
            if UIDevice.isIPhoneX {
                return -30.0
            }
            return -24.0
        }
    }
    
    private var modalTopDistance: CGFloat {
        if UIDevice.isIPhoneX {
            return shouldCover ? 0.0 : 50.0
        }
        
        return shouldCover ? 0.0 : 30.0
    }
    
    private var scaleFactor: CGFloat = 0.95
    private var sourceOldCornerRadius: CGFloat = 0.0
    private var targetOldCornerRadius: CGFloat = 0.0
    private lazy var dimView: UIView = {
        let view = UIView()
        view.backgroundColor = .black
        view.alpha = 0.0
        view.tag = KagomeTransitionController.dimViewTag
        return view
    }()
    
    private static let dimViewTag: Int = 332951735
    private static let snapshotViewTag: Int = 521866280
    
    // MARK: - UIViewControllerAnimatedTransitioning
    
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.50
    }
    
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let duration = transitionDuration(using: transitionContext)
        let containerView = transitionContext.containerView
        
        guard let fromController = transitionContext.viewController(forKey: .from),
            let snapshottedFromView = transitionContext.viewController(forKey: .from)?.view.snapshotView(afterScreenUpdates: false),
            let toController = transitionContext.viewController(forKey: .to) else { return }
        
        if isPresenting {
            containerView.addSubview(snapshottedFromView)
            containerView.addSubview(dimView)
            containerView.addSubview(toController.view)
            
            snapshottedFromView.tag = KagomeTransitionController.snapshotViewTag
            snapshottedFromView.frame = fromController.view.frame
            dimView.frame = containerView.bounds
            
            toController.view.frame = CGRect(x: 0.0,
                                             y: containerView.bounds.height,
                                             width: containerView.bounds.width,
                                             height: containerView.bounds.height)
            sourceOldCornerRadius = fromController.view.layer.cornerRadius
            targetOldCornerRadius = toController.view.layer.cornerRadius
            
            UIView.animate(withDuration: duration, animations: {
                fromController.view.layer.cornerRadius = self.cornerRadius
                fromController.view.layer.masksToBounds = true
                snapshottedFromView.layer.cornerRadius = self.cornerRadius
                snapshottedFromView.layer.masksToBounds = true
                toController.view.layer.cornerRadius = self.cornerRadius
                toController.view.layer.masksToBounds = true
                
                self.dimView.alpha = 0.6
                
                let fromTransfrom: CGAffineTransform = {
                    if fromController.view.frame == containerView.bounds {
                        let scale = CGAffineTransform(scaleX: self.scaleFactor * 0.97, y: self.scaleFactor * 0.97)
                        let translate = CGAffineTransform(translationX: 0.0, y: self.rearViewTranslation(isFirst: true))
                        return scale.concatenating(translate)
                    } else {
                        let scale = CGAffineTransform(scaleX: self.scaleFactor, y: self.scaleFactor)
                        let translate = CGAffineTransform(translationX: 0.0, y: self.rearViewTranslation(isFirst: false))
                        return scale.concatenating(translate)
                    }
                }()
                
                fromController.view.transform = fromTransfrom
                snapshottedFromView.transform = fromTransfrom
                
                if self.shouldCover {
                    toController.view.frame = transitionContext.finalFrame(for: toController)
                } else {
                    toController.view.frame = {
                        var frame = transitionContext.finalFrame(for: toController).offsetBy(dx: 0.0, dy: self.modalTopDistance)
                        frame.size.height -= self.modalTopDistance
                        return frame
                    }()
                }
            }, completion: { _ in
                transitionContext.completeTransition(true)
            })
        } else {
            let dimView = containerView.subviews.first(where: { $0.tag == KagomeTransitionController.dimViewTag })
            let snapshotView = containerView.subviews.first(where: { $0.tag == KagomeTransitionController.snapshotViewTag })
            
            UIView.animate(withDuration: duration, animations: {
                dimView?.alpha = 0.0
                snapshotView?.transform = .identity
                toController.view.transform = .identity
                
                fromController.view.layer.cornerRadius = self.cornerRadius
                fromController.view.layer.masksToBounds = true
                snapshottedFromView.layer.cornerRadius = self.cornerRadius
                snapshottedFromView.layer.masksToBounds = true
                toController.view.layer.cornerRadius = self.cornerRadius
                toController.view.layer.masksToBounds = true
                
                fromController.view.frame = CGRect(x: 0.0,
                                                   y: containerView.bounds.height,
                                                   width: containerView.bounds.width,
                                                   height: containerView.bounds.height)
            }, completion: { _ in
                dimView?.removeFromSuperview()
                snapshotView?.removeFromSuperview()
                fromController.view.removeFromSuperview()
                transitionContext.completeTransition(true)
            })
        }
    }
}