Skip to content

Instantly share code, notes, and snippets.

@ZhipingYang
Created November 15, 2019 03:22
Show Gist options
  • Save ZhipingYang/e15c18b1b8bb371199a6f801bdc05935 to your computer and use it in GitHub Desktop.
Save ZhipingYang/e15c18b1b8bb371199a6f801bdc05935 to your computer and use it in GitHub Desktop.
UIViewControllerAnimatedTransitioning, UIPresentationController, UIViewControllerTransitioningDelegate
//
// 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