// // ShareScreenPresentation.swift // RoomsController // // Created by Daniel Yang on 2019/3/15. // Copyright © 2019 RingCentral. All rights reserved. // import ObjectiveC import UIKit private var popupBackColor: UIColor { return UIColor.black.withAlphaComponent(0.4) } extension UIScreen { class func rectOfCenter(width: CGFloat, height: CGFloat) -> CGRect { let size = UIScreen.main.bounds.size return CGRect(x: (size.width - width) / 2.0, y: (size.height - height) / 2.0, width: width, height: height) } } typealias TimeDuration = (present: TimeInterval?, dismiss: TimeInterval?) enum PopupAnimateType { case fade(TimeDuration?) case bottom(TimeDuration?) case pop(TimeDuration?) } class PopupAnimate: NSObject, UIViewControllerAnimatedTransitioning { static let durationTime: TimeInterval = 0.3 private struct PopupTransform { static var identity = CGAffineTransform.identity static var bottom = CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.size.height) static var small = CGAffineTransform(scaleX: 0.5, y: 0.5) } var isPresentation: Bool var type: PopupAnimateType init(isPresentation: Bool, type: PopupAnimateType) { self.isPresentation = isPresentation self.type = type } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { if case let .fade(duration) = type { return (isPresentation ? duration?.present : duration?.dismiss) ?? PopupAnimate.durationTime } else if case let .bottom(duration) = type { return (isPresentation ? duration?.present : duration?.dismiss) ?? PopupAnimate.durationTime } else if case let .pop(duration) = type { return (isPresentation ? duration?.present : duration?.dismiss) ?? PopupAnimate.durationTime } return PopupAnimate.durationTime } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let animateView = transitionContext.view(forKey: isPresentation ? .to : .from) else { transitionContext.completeTransition(!transitionContext.transitionWasCancelled) return } let containerView = transitionContext.containerView let fromVC = transitionContext.viewController(forKey: .from) let toVC = transitionContext.viewController(forKey: .to) if isPresentation { containerView.addSubview(animateView) } fromVC?.beginAppearanceTransition(false, animated: true) toVC?.beginAppearanceTransition(true, animated: true) let prepare = { () in if case .bottom = self.type { animateView.alpha = 1 } else { animateView.alpha = self.isPresentation ? 0 : 1 } if case .bottom = self.type, self.isPresentation { animateView.transform = PopupTransform.bottom } else if case .pop = self.type, self.isPresentation { animateView.transform = PopupTransform.small } } let changed = { () in if case .bottom = self.type { animateView.alpha = 1 } else { animateView.alpha = self.isPresentation ? 1 : 0 } if case .bottom = self.type { let trans = self.isPresentation ? PopupTransform.identity : PopupTransform.bottom animateView.transform = trans } else if case .pop = self.type { let trans = self.isPresentation ? PopupTransform.identity : PopupTransform.small animateView.transform = trans } } let finished = { (finished: Bool) in animateView.transform = PopupTransform.identity fromVC?.endAppearanceTransition() toVC?.endAppearanceTransition() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } prepare() let duration = transitionDuration(using: transitionContext) if case .pop = type, isPresentation { UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1, options: .curveEaseIn, animations: changed, completion: finished) } else { UIView.animate(withDuration: duration, animations: changed, completion: finished) } } } fileprivate class PopupPresentation: UIPresentationController { var tapBackAction: (() -> Void)? var config: PopupMiddleware? { didSet { guard let config = config else { return } presentedView?.clipsToBounds = true presentedView?.layer.cornerRadius = config.cornerRadius tapBackAction = config.tapBackAction } } private lazy var backView: UIView = { UIView().then { $0.backgroundColor = self.config?.backgroundColor ?? popupBackColor $0.translatesAutoresizingMaskIntoConstraints = false $0.addGestureRecognizer( UITapGestureRecognizer(target: self, action: #selector(touchBackViewAction)) ) } }() private var contentFrame: CGRect { return self.config?.frame ?? UIScreen.main.bounds } override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() guard let containerView = containerView else { return } containerView.insertSubview(backView, at: 0) backView.makeConstraintsToBindToSuperview() backView.alpha = 0 presentedView?.frame = contentFrame guard let transitionCoordinator = presentedViewController.transitionCoordinator else { backView.alpha = 1 return } transitionCoordinator.animate(alongsideTransition: { [weak self] context in self?.backView.alpha = 1 }, completion: nil) } override func dismissalTransitionWillBegin() { super.dismissalTransitionWillBegin() guard let transitionCoordinator = presentedViewController.transitionCoordinator else { backView.alpha = 0 return } transitionCoordinator.animate(alongsideTransition: { [weak self] context in self?.backView.alpha = 0 }, completion: nil) } @objc func dismissAction() { presentedViewController.dismiss(animated: true, completion: nil) } @objc func touchBackViewAction() { containerView?.endEditing(true) if config?.dissmissWhenTouchBack ?? false { dismissAction() } else { tapBackAction?() } } } // MARK: Main class PopupMiddleware: NSObject { // public, manual set var frame: CGRect = UIScreen.main.bounds var cornerRadius: CGFloat = 12 var dissmissWhenTouchBack: Bool = true var type: PopupAnimateType = .bottom(nil) var backgroundColor: UIColor = popupBackColor var tapBackAction: (() -> Void)? fileprivate var dismissedAnimate: UIViewControllerAnimatedTransitioning { return PopupAnimate(isPresentation: false, type: type) } fileprivate var presentingAnimate: UIViewControllerAnimatedTransitioning { return PopupAnimate(isPresentation: true, type: type) } } extension PopupMiddleware: UIViewControllerTransitioningDelegate { func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return PopupPresentation(presentedViewController: presented, presenting: presenting).then { $0.config = self } } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return dismissedAnimate } func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return presentingAnimate } } protocol PopupAnimatePresentationProtocol { func popupViewController(by: UIViewController, config: ((_ info: PopupMiddleware) -> Void)?, completion: (() -> Void)?) } extension UIViewController { private struct PopupAssociateKey { static var transitioningDelegate = 0 } fileprivate var popTransitioningDelegate: PopupMiddleware { if let obj = objc_getAssociatedObject(self, &PopupAssociateKey.transitioningDelegate) as? PopupMiddleware { return obj } return PopupMiddleware().then { objc_setAssociatedObject(self, &PopupAssociateKey.transitioningDelegate, $0, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } extension PopupAnimatePresentationProtocol where Self: UIViewController { func popupViewController(by: UIViewController, config: ((_ info: PopupMiddleware) -> Void)? = nil, completion: (() -> Void)? = nil) { // config config?(popTransitioningDelegate) // set transfrom modalPresentationStyle = .custom transitioningDelegate = popTransitioningDelegate by.present(self, animated: true, completion: completion) } }