Created
November 15, 2019 03:22
-
-
Save ZhipingYang/e15c18b1b8bb371199a6f801bdc05935 to your computer and use it in GitHub Desktop.
UIViewControllerAnimatedTransitioning, UIPresentationController, UIViewControllerTransitioningDelegate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment