Skip to content

Instantly share code, notes, and snippets.

@RaajeevChandran
Created April 7, 2025 16:57
Show Gist options
  • Select an option

  • Save RaajeevChandran/d897ec3d7695e78d03fe8948311f1b7e to your computer and use it in GitHub Desktop.

Select an option

Save RaajeevChandran/d897ec3d7695e78d03fe8948311f1b7e to your computer and use it in GitHub Desktop.
class BottomSheetTransition: UIPercentDrivenInteractiveTransition {
private var dismissalAnimator: UIViewPropertyAnimator?
private var presentationAnimator: UIViewPropertyAnimator?
private let animationDuration = 0.5
private let dampingRatio: Double = 0.9
var dismissFractionComplete: CGFloat {
dismissalAnimator?.fractionComplete ?? .zero
}
var presenting = true
}
extension BottomSheetTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
animationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let animator = interruptibleAnimator(using: transitionContext)
animator.startAnimation()
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
presenting ? presentationAnimator(using: transitionContext) : dismissAnimator(using: transitionContext)
}
private func presentationAnimator(
using transitionContext: UIViewControllerContextTransitioning
) -> UIViewImplicitlyAnimating {
guard let presentationAnimator else {
guard
let toViewController = transitionContext.viewController(forKey: .to),
let toView = transitionContext.view(forKey: .to)
else {
return UIViewPropertyAnimator()
}
let animator = UIViewPropertyAnimator(
duration: transitionDuration(using: transitionContext),
dampingRatio: dampingRatio
)
presentationAnimator = animator
toView.frame = transitionContext.finalFrame(for: toViewController)
toView.frame.origin.y = transitionContext.containerView.frame.maxY
transitionContext.containerView.addSubview(toView)
animator.addAnimations {
toView.frame = transitionContext.finalFrame(for: toViewController)
}
animator.addCompletion { [weak self] position in
self?.presentationAnimator = nil
guard case .end = position else {
transitionContext.completeTransition(false)
return
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return animator
}
return presentationAnimator
}
private func dismissAnimator(
using transitionContext: UIViewControllerContextTransitioning
) -> UIViewImplicitlyAnimating {
guard let dismissAnimator = self.dismissalAnimator else {
guard let fromView = transitionContext.view(forKey: .from) else {
return UIViewPropertyAnimator()
}
let animator = UIViewPropertyAnimator(
duration: transitionDuration(using: transitionContext),
dampingRatio: dampingRatio
)
dismissalAnimator = animator
animator.addAnimations {
fromView.frame.origin.y = fromView.frame.maxY
}
animator.addCompletion { [weak self] position in
self?.dismissalAnimator = nil
guard case .end = position else {
transitionContext.completeTransition(false)
return
}
fromView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return animator
}
return dismissAnimator
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment