如何从顶部模态呈现ViewController,实现类似dismiss的动画效果?
Hey there! I get that you're trying to present a view controller modally from the top with a dismiss-style animation, and ran into trouble with inconsistent timing when using CATransition. Let's sort this out properly!
When you add a CATransition directly to the window's layer alongside present(vc, animated: false), the system's internal view hierarchy changes for the presentation can desync with your custom layer animation. This leads to the inconsistent duration you're seeing—since the two processes aren't tied together in a controlled way.
Using UIViewControllerAnimatedTransitioning gives you full control over the animation's timing, behavior, and sync with the presentation lifecycle. Here's how to implement it:
Step 1: Create the Presentation Animator Class
This class will handle the "slide down from top" animation for presenting your VC:
class TopSlideInPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { // Explicitly set your desired duration here return 0.25 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toViewController = transitionContext.viewController(forKey: .to), let toView = transitionContext.view(forKey: .to) else { transitionContext.completeTransition(false) return } let containerView = transitionContext.containerView // Start with the view positioned off-screen above the top edge toView.frame = CGRect( x: 0, y: -containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height ) containerView.addSubview(toView) // Animate the view into the screen UIView.animate( withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveEaseInOut, animations: { toView.frame = containerView.bounds }, completion: { finished in transitionContext.completeTransition(finished && !transitionContext.transitionWasCancelled) } ) } }
Step 2: Add the Dismiss Animator (Optional but Recommended)
If you want the dismiss animation to match the presentation style, create a corresponding animator:
class TopSlideOutDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.25 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromView = transitionContext.view(forKey: .from) else { transitionContext.completeTransition(false) return } let containerView = transitionContext.containerView // Animate the view back off-screen above the top edge UIView.animate( withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveEaseInOut, animations: { fromView.frame = CGRect( x: 0, y: -containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height ) }, completion: { finished in fromView.removeFromSuperview() transitionContext.completeTransition(finished && !transitionContext.transitionWasCancelled) } ) } }
Step 3: Set Up the Transition in Your Presenting View Controller
First, update your presentation code to use custom transitions:
let vc = storyboard?.instantiateViewController(withIdentifier: "DownloadAudio") as! DownloadAudioViewController vc.modalPresentationStyle = .custom vc.transitioningDelegate = self present(vc, animated: true, completion: nil)
Then, conform your presenting view controller to UIViewControllerTransitioningDelegate to return your animators:
extension YourPresentingViewController: UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return TopSlideInPresentationAnimator() } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return TopSlideOutDismissAnimator() } }
This approach ensures your animation duration stays consistent because you're directly tying the animation to the presentation/dismissal lifecycle of the view controller—no desync with window layer animations!
内容的提问来源于stack exchange,提问作者Safeen Azad

