Something went wrong on our end
BottomTransition.swift 4.38 KiB
import UIKit
import Combine
import SnapKit
import Shared
final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
enum Direction {
case present
case dismiss
}
var direction: Direction = .present
private let onDismissal: EmptyClosure
private weak var darkOverlayView: UIControl?
private weak var topConstraint: Constraint?
private weak var bottomConstraint: Constraint?
private var cancellables = Set<AnyCancellable>()
private var presentedConstraints: [NSLayoutConstraint] = []
private var dismissedConstraints: [NSLayoutConstraint] = []
init(onDismissal: @escaping EmptyClosure) {
self.onDismissal = onDismissal
super.init()
}
func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.5 }
func animateTransition(using context: UIViewControllerContextTransitioning) {
switch direction {
case .present:
present(using: context)
case .dismiss:
dismiss(using: context)
}
}
private func present(using context: UIViewControllerContextTransitioning) {
guard let presentingController = context.viewController(forKey: .from),
let presentedView = context.view(forKey: .to) else {
context.completeTransition(false)
return
}
let darkOverlayView = UIControl()
self.darkOverlayView = darkOverlayView
darkOverlayView.alpha = 0.0
darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
context.containerView.addSubview(darkOverlayView)
darkOverlayView.frame = context.containerView.bounds
darkOverlayView
.publisher(for: .touchUpInside)
.sink { [weak presentingController] _ in
presentingController?.dismiss(animated: true)
}.store(in: &cancellables)
context.containerView.addSubview(presentedView)
presentedView.translatesAutoresizingMaskIntoConstraints = false
presentedConstraints = [
presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor),
presentedView.topAnchor.constraint(
greaterThanOrEqualTo: context.containerView.safeAreaLayoutGuide.topAnchor,
constant: 60
)
]
dismissedConstraints = [
presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor)
]
NSLayoutConstraint.activate(dismissedConstraints)
context.containerView.setNeedsLayout()
context.containerView.layoutIfNeeded()
NSLayoutConstraint.deactivate(dismissedConstraints)
NSLayoutConstraint.activate(presentedConstraints)
UIView.animate(
withDuration: transitionDuration(using: context),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: .curveEaseInOut,
animations: {
darkOverlayView.alpha = 1.0
context.containerView.setNeedsLayout()
context.containerView.layoutIfNeeded()
},
completion: { _ in
context.completeTransition(true)
})
}
private func dismiss(using context: UIViewControllerContextTransitioning) {
NSLayoutConstraint.deactivate(presentedConstraints)
NSLayoutConstraint.activate(dismissedConstraints)
UIView.animate(
withDuration: transitionDuration(using: context),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: .curveEaseInOut,
animations: { [weak darkOverlayView] in
darkOverlayView?.alpha = 0.0
context.containerView.setNeedsLayout()
context.containerView.layoutIfNeeded()
},
completion: { [weak self] _ in
context.completeTransition(true)
self?.onDismissal()
})
}
}