You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从顶部模态呈现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!

The Issue with Your Current CATransition Approach

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.

Better Solution: Custom View Controller Transition

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)
            }
        )
    }
}

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:37:44