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

SwiftUI自定义视图转场实现:从右到左全屏呈现,支持双视图动画

嘿,我之前正好做过一模一样的需求!下面给你分享一套方案,能实现和UIKit里UIViewControllerAnimatedTransitioning一样灵活的自定义转场——支持从右到左的全屏呈现,还能分别控制父视图和被呈现视图的动画效果,完全满足你的需求。

核心思路

SwiftUI的原生转场(比如fullScreenCover)虽然便捷,但灵活性远不如UIKit的转场系统。我们可以通过桥接UIKit的转场框架,复用UIViewControllerAnimatedTransitioning和UIViewControllerTransitioningDelegate来实现完全自定义的动画逻辑,同时保持SwiftUI的声明式语法体验。

实现步骤

1. 自定义动画过渡控制器

首先创建一个遵循UIViewControllerAnimatedTransitioning的类,这是控制转场动画的核心,你可以在这里定义呈现/消失时的所有动画细节:

class CustomTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let isPresenting: Bool
    
    init(isPresenting: Bool) {
        self.isPresenting = isPresenting
        super.init()
    }
    
    // 自定义过渡时长
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }
    
    // 核心动画逻辑
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let toVC = transitionContext.viewController(forKey: .to),
              let fromVC = transitionContext.viewController(forKey: .from) else {
            transitionContext.completeTransition(false)
            return
        }
        
        if isPresenting {
            // 呈现动画:被呈现视图从右侧滑入,父视图缩小+变暗
            toVC.view.frame = containerView.frame.offsetBy(dx: containerView.bounds.width, dy: 0)
            containerView.addSubview(toVC.view)
            
            UIView.animate(withDuration: transitionDuration(using: transitionContext), 
                           delay: 0, 
                           options: .curveEaseInOut) {
                toVC.view.frame = containerView.frame
                fromVC.view.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
                fromVC.view.alpha = 0.7
            } completion: { _ in
                transitionContext.completeTransition(true)
            }
        } else {
            // 消失动画:被呈现视图滑出右侧,父视图恢复原状
            UIView.animate(withDuration: transitionDuration(using: transitionContext), 
                           delay: 0, 
                           options: .curveEaseInOut) {
                fromVC.view.frame = containerView.frame.offsetBy(dx: containerView.bounds.width, dy: 0)
                toVC.view.transform = .identity
                toVC.view.alpha = 1.0
            } completion: { _ in
                transitionContext.completeTransition(true)
            }
        }
    }
}

2. 创建转场代理类

接下来实现UIViewControllerTransitioningDelegate,用来告诉系统使用我们自定义的动画控制器:

class CustomTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, 
                             presenting: UIViewController, 
                             source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return CustomTransitionAnimator(isPresenting: true)
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return CustomTransitionAnimator(isPresenting: false)
    }
}

3. 包装SwiftUI视图的容器

用UIViewControllerRepresentable把SwiftUI视图包装成UIKit的UIHostingController,并绑定呈现状态:

struct TransitionContainerView<Content: View>: UIViewControllerRepresentable {
    let content: Content
    @Binding var isPresented: Bool
    private let transitionDelegate = CustomTransitionDelegate()
    
    func makeUIViewController(context: Context) -> UIHostingController<Content> {
        let hostingVC = UIHostingController(rootView: content)
        hostingVC.modalPresentationStyle = .custom
        hostingVC.transitioningDelegate = transitionDelegate
        return hostingVC
    }
    
    func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
        // 更新视图内容
        uiViewController.rootView = content
        
        // 处理呈现/消失状态
        if isPresented, !uiViewController.isBeingPresented {
            uiViewController.presentingViewController?.present(uiViewController, animated: true)
        } else if !isPresented, uiViewController.presentingViewController != nil {
            uiViewController.dismiss(animated: true)
        }
    }
}

4. 封装成SwiftUI风格的修饰符

为了让使用更便捷,我们给View扩展一个自定义修饰符,用法和原生的fullScreenCover一致:

extension View {
    func customFullScreenTransition<Content: View>(isPresented: Binding<Bool>, 
                                                  @ViewBuilder content: @escaping () -> Content) -> some View {
        self.background(
            TransitionContainerView(content: content(), isPresented: isPresented)
        )
    }
}
示例用法

现在你可以像使用原生转场一样,轻松实现自定义的从右到左全屏转场:

struct ContentView: View {
    @State private var isPresentingDetail = false
    
    var body: some View {
        Button("打开详情页") {
            isPresentingDetail = true
        }
        .customFullScreenTransition(isPresented: $isPresentingDetail) {
            Color.blue
                .overlay(
                    Button("关闭") {
                        isPresentingDetail = false
                    }
                    .foregroundColor(.white)
                    .font(.title)
                )
        }
    }
}
进阶优化建议
  • 可配置动画参数:可以把动画时长、缩放比例、透明度等做成参数,让转场更灵活适配不同场景。
  • 交互式转场:如果需要滑动返回等交互效果,可以实现UIViewControllerInteractiveTransitioning协议,添加手势识别器。
  • 自定义转场方向:修改animateTransition里的offsetBy参数,就能实现从左、上、下等不同方向的入场动画。

内容的提问来源于stack exchange,提问作者Joseph Francis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:08:01