Skip to content

Instantly share code, notes, and snippets.

@jamesrochabrun
Created June 30, 2020 22:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesrochabrun/df696e7abe33d864400ab1a179ded5a8 to your computer and use it in GitHub Desktop.
Save jamesrochabrun/df696e7abe33d864400ab1a179ded5a8 to your computer and use it in GitHub Desktop.
class FrameTransitionAnimator: NSObject {
enum OverlayType {
case dim
case blur(style: UIBlurEffect.Style)
}
enum DimTransitionMode: Int {
case present, dismiss
}
private let overlayType: OverlayType
private let dismissImpact = UIImpactFeedbackGenerator(style: .light)
private let duration: CGFloat
private let isOverlayedFromMaxY: Bool
private (set)var blurEffectStyle: UIBlurEffect.Style = .regular
private var absoluteFrame: CGRect = CGRect.zero
private var dimView: UIView?
private var transitionMode: DimTransitionMode = .present
private lazy var vibrancyEffectView: UIVisualEffectView = {
UIVisualEffectView(effect: UIBlurEffect(style: .regular))
}()
init(duration: CGFloat, overlayType: OverlayType, overlayFromMaxY: Bool = false) {
self.duration = duration
self.overlayType = overlayType
self.isOverlayedFromMaxY = overlayFromMaxY
switch overlayType {
case .blur(let style): blurEffectStyle = style
case .dim: break
}
}
func transitionMode(_ transMode: DimTransitionMode, startingFrame: CGRect = CGRect.zero) {
transitionMode = transMode
absoluteFrame = startingFrame
}
}
extension FrameTransitionAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
transitionMode == .present ? presentInTransitionContext(transitionContext) : dismissInTransitionContext(transitionContext)
}
fileprivate func presentInTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
guard let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
return
}
let containerView = transitionContext.containerView
switch overlayType {
case .dim:
dimView = UIView()
dimView?.falcon_observeTheme { [weak self] theme in
self?.dimView?.backgroundColor = theme.overlayVeilBackgroundColor
}
dimView?.alpha = 0
if let dimView = dimView {
setupOverlayInContainer(containerView, overlay: dimView)
}
case .blur:
vibrancyEffectView.effect = nil
self.vibrancyEffectView.backgroundColor = .clear
setupOverlayInContainer(containerView, overlay: vibrancyEffectView)
}
containerView.addSubview(presentedView)
// var newFrame = absoluteFrame
// newFrame.origin.y = 0
// newFrame.origin.x = 0
// newFrame.size.width = containerView.frame.size.width
presentedView.frame = absoluteFrame
containerView.layoutIfNeeded()
UIView.animate(withDuration: 0.4, animations: {
switch self.overlayType {
case .dim: self.dimView?.alpha = 1
case .blur(_):
self.vibrancyEffectView.effect = UIBlurEffect(style: self.blurEffectStyle)
self.vibrancyEffectView.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.6)
}
presentedView.frame = containerView.frame
containerView.layoutIfNeeded()
self.dismissImpact.prepare()
self.dismissImpact.impactOccurred()
}) { (_) in
let didComplete = !transitionContext.transitionWasCancelled
transitionContext.completeTransition(didComplete)
}
}
/// Constraints the overlay based on the `overlayFromMaxY` property.
private func setupOverlayInContainer(_ container: UIView, overlay: UIView) {
container.addSubview(overlay)
guard isOverlayedFromMaxY else {
overlay.fillSuperview()
return
}
overlay.anchor(top: container.topAnchor,
leading: container.leadingAnchor,
bottom: container.bottomAnchor,
trailing: container.trailingAnchor,
padding: UIEdgeInsets.padding(top: absoluteFrame.maxY))
}
fileprivate func dismissInTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
guard let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {
return
}
let containerView = transitionContext.containerView
returningView.layer.cornerRadius = TopOfInboxViewSpecs.defaultRadius.value
var newFrame = absoluteFrame
// newFrame.origin.y = 0
// newFrame.origin.x = 0
// newFrame.size.width = containerView.frame.size.width
containerView.layoutIfNeeded()
UIView.animate(withDuration: 0.3, animations: {
switch self.overlayType {
case .dim: self.dimView?.alpha = 0
case .blur(_):
self.vibrancyEffectView.backgroundColor = .clear
self.vibrancyEffectView.effect = nil
}
returningView.transform = .identity
returningView.frame = newFrame
containerView.layoutIfNeeded()
}) { (_) in
let didComplete = !transitionContext.transitionWasCancelled
transitionContext.completeTransition(didComplete)
switch self.overlayType {
case .dim: self.dimView?.removeFromSuperview()
case .blur(_): break
}
self.dismissImpact.prepare()
self.dismissImpact.impactOccurred()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment