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

如何为导航控制器的Push/Pop视图控制器添加滑动动画效果?

我来帮你搞定这个滑动切换的需求!你想要的这种滑动导航栏就能完成Push/Pop的效果,其实有两种成熟的实现思路,我给你一步步拆解清楚:


方案一:基于UINavigationController自定义过渡动画和手势

这个方案更贴合原生导航栈的逻辑,适合有层级关系的页面(比如列表页→详情页),你之前尝试的代理方法方向是对的,只是动画逻辑和手势触发需要调整:

1. 自定义页面过渡动画

先写一个符合UIViewControllerAnimatedTransitioning协议的动画类,实现左右滑动的切换效果:

class SlideTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    private let isPush: Bool
    
    init(isPush: Bool) {
        self.isPush = isPush
        super.init()
    }
    
    // 动画时长
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.35
    }
    
    // 核心动画逻辑
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        guard let fromVC = transitionContext.viewController(forKey: .from),
              let toVC = transitionContext.viewController(forKey: .to) else {
            transitionContext.completeTransition(false)
            return
        }
        
        let screenWidth = UIScreen.main.bounds.width
        // 初始化目标页面的位置
        toVC.view.frame = CGRect(x: isPush ? screenWidth : -screenWidth, y: 0, width: screenWidth, height: container.bounds.height)
        container.addSubview(toVC.view)
        
        // 执行滑动动画
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            fromVC.view.frame = CGRect(x: self.isPush ? -screenWidth : screenWidth, y: 0, width: screenWidth, height: container.bounds.height)
            toVC.view.frame = container.bounds
        } _ in
            fromVC.view.frame = container.bounds
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

2. 实现导航控制器代理

创建代理类,在切换时返回自定义动画:

class NavTransitionDelegate: NSObject, UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return SlideTransitionAnimator(isPush: operation == .push)
    }
}

3. 给导航栏添加滑动手势

自定义导航控制器,给导航栏绑定拖拽手势,根据滑动方向触发Push/Pop:

class CustomNavController: UINavigationController {
    private let panGesture = UIPanGestureRecognizer()
    private let transitionDelegate = NavTransitionDelegate()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = transitionDelegate
        // 给导航栏添加滑动手势
        navigationBar.addGestureRecognizer(panGesture)
        panGesture.addTarget(self, action: #selector(handleNavBarPan(_:)))
    }
    
    @objc private func handleNavBarPan(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: view)
        guard gesture.state == .ended else { return }
        
        if translation.x > 50 {
            // 向右滑动 → Pop页面
            popViewController(animated: true)
        } else if translation.x < -50 {
            // 向左滑动 → Push页面(这里替换成你要跳转的目标VC)
            let nextVC = TargetViewController()
            pushViewController(nextVC, animated: true)
        }
    }
}

方案二:用UIPageViewController实现平级滑动切换

如果你的页面是平级关系(比如多个分类页),UIPageViewController本身就支持左右滑动切换,只需自定义导航栏保持和GIF一致的样式:

1. 构建容器页面+自定义导航栏

创建一个容器VC,顶部放自定义导航栏,下方嵌入UIPageViewController:

class ContainerVC: UIViewController {
    private let customNavBar = CustomNavigationBar()
    private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
    private let pageList: [UIViewController] = [HomeVC(), CategoryVC(), ProfileVC()]
    private var currentPageIndex = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCustomNavBar()
        setupPageViewController()
        bindNavBarGesture()
    }
    
    // 初始化自定义导航栏
    private func setupCustomNavBar() {
        customNavBar.frame = CGRect(x: 0, y: view.safeAreaInsets.top, width: view.bounds.width, height: 44)
        customNavBar.title = pageList[currentPageIndex].title
        customNavBar.leftButton.isHidden = currentPageIndex == 0
        view.addSubview(customNavBar)
    }
    
    // 初始化PageViewController
    private func setupPageViewController() {
        pageVC.dataSource = self
        pageVC.delegate = self
        pageVC.setViewControllers([pageList[currentPageIndex]], direction: .forward, animated: false)
        addChild(pageVC)
        pageVC.view.frame = CGRect(x: 0, y: customNavBar.frame.maxY, width: view.bounds.width, height: view.bounds.height - customNavBar.frame.height - view.safeAreaInsets.bottom)
        view.addSubview(pageVC.view)
        pageVC.didMove(toParent: self)
    }
    
    // 给导航栏绑定滑动手势
    private func bindNavBarGesture() {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleNavBarPan(_:)))
        customNavBar.addGestureRecognizer(panGesture)
    }
    
    // 处理导航栏滑动逻辑
    @objc private func handleNavBarPan(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: view)
        guard gesture.state == .ended else { return }
        
        if translation.x > 50 && currentPageIndex > 0 {
            currentPageIndex -= 1
            pageVC.setViewControllers([pageList[currentPageIndex]], direction: .reverse, animated: true)
            updateNavBarState()
        } else if translation.x < -50 && currentPageIndex < pageList.count - 1 {
            currentPageIndex += 1
            pageVC.setViewControllers([pageList[currentPageIndex]], direction: .forward, animated: true)
            updateNavBarState()
        }
    }
    
    // 更新导航栏状态(标题、按钮显示)
    private func updateNavBarState() {
        customNavBar.title = pageList[currentPageIndex].title
        customNavBar.leftButton.isHidden = currentPageIndex == 0
    }
}

// 实现PageViewController的数据源和代理
extension ContainerVC: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let index = pageList.firstIndex(of: viewController), index > 0 else { return nil }
        currentPageIndex = index - 1
        updateNavBarState()
        return pageList[currentPageIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let index = pageList.firstIndex(of: viewController), index < pageList.count - 1 else { return nil }
        currentPageIndex = index + 1
        updateNavBarState()
        return pageList[currentPageIndex]
    }
}

// 自定义导航栏类(完全复刻系统导航栏样式)
class CustomNavigationBar: UIView {
    let titleLabel = UILabel()
    let leftButton = UIButton(type: .system)
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        setupSubviews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupSubviews() {
        // 标题标签
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
        addSubview(titleLabel)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
        
        // 返回按钮
        leftButton.setTitle("Back", for: .normal)
        addSubview(leftButton)
        leftButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            leftButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            leftButton.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }
    
    var title: String? {
        didSet { titleLabel.text = title }
    }
}

两种方案的适用场景

  • 如果页面是层级依赖关系(比如列表→详情),优先选方案一,符合原生导航栈的生命周期逻辑;
  • 如果页面是平级切换关系(比如多个分类页),方案二更简洁,UIPageViewController自带滑动切换的基础逻辑。

内容的提问来源于stack exchange,提问作者Mitesh Dobareeya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 19:42:47