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

如何在UINavigationBar中实现自定义返回按钮到默认按钮的动画过渡?

实现自定义返回按钮到默认按钮的过渡动画方案

针对你要复刻App Store/Apple Music的返回按钮过渡效果需求,由于自定义按钮带有视觉模糊效果(VisualEffect),无法通过系统backIndicatorImage系列属性实现,这里给出基于自定义leftBarButtonItem+手动控制过渡动画的可行方案:

核心思路

放弃系统自带的返回按钮样式管理,自己接管导航栈切换时的按钮替换逻辑:

  1. 在需要自定义返回按钮的页面,设置带视觉效果的自定义视图作为leftBarButtonItem
  2. 利用导航控制器的push/pop生命周期钩子,在页面切换时触发自定义按钮与默认返回按钮的过渡动画
  3. 通过UIView动画实现缩放、淡入淡出的平滑切换,匹配系统原生动画节奏

具体实现步骤与代码示例

1. 自定义带视觉效果的返回按钮视图

先封装一个包含模糊效果的自定义按钮,替代系统默认返回按钮:

import UIKit

class BlurBackButtonView: UIView {
    private let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial))
    private let backButton = UIButton(type: .system)
    
    // 点击回调,用于触发页面pop
    var tapHandler: (() -> Void)?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI() {
        // 配置模糊视图样式
        blurEffectView.layer.cornerRadius = 12
        blurEffectView.clipsToBounds = true
        addSubview(blurEffectView)
        blurEffectView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            blurEffectView.edgesAnchor.constraint(equalTo: self.edgesAnchor)
        ])
        
        // 配置自定义按钮图标与事件
        backButton.setImage(UIImage(systemName: "chevron.left.circle.fill"), for: .normal)
        backButton.tintColor = .systemBlue
        backButton.addTarget(self, action: #selector(onBackTap), for: .touchUpInside)
        blurEffectView.contentView.addSubview(backButton)
        backButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            backButton.centerXAnchor.constraint(equalTo: blurEffectView.centerXAnchor),
            backButton.centerYAnchor.constraint(equalTo: blurEffectView.centerYAnchor),
            backButton.widthAnchor.constraint(equalToConstant: 32),
            backButton.heightAnchor.constraint(equalToConstant: 32)
        ])
    }
    
    @objc private func onBackTap() {
        tapHandler?()
    }
}

2. 自定义导航控制器管理动画切换

通过自定义导航控制器,监听页面切换事件,处理按钮的动画过渡:

import UIKit

class TransitionNavigationController: UINavigationController, UINavigationControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
    
    // Push页面时:自定义按钮 → 默认按钮的过渡
    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        guard let currentVC = topViewController, animated else {
            super.pushViewController(viewController, animated: animated)
            return
        }
        
        // 仅当当前页面使用自定义返回按钮时才触发动画
        guard let customBackView = currentVC.navigationItem.leftBarButtonItem?.customView as? BlurBackButtonView else {
            super.pushViewController(viewController, animated: animated)
            return
        }
        
        super.pushViewController(viewController, animated: animated)
        
        // 创建系统风格的默认返回按钮
        let defaultBackButton = UIButton(type: .system)
        defaultBackButton.setImage(UIImage(systemName: "chevron.left")?.withRenderingMode(.alwaysTemplate), for: .normal)
        defaultBackButton.tintColor = .label
        defaultBackButton.frame = customBackView.frame
        navigationBar.addSubview(defaultBackButton)
        
        // 执行过渡动画
        UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
            customBackView.alpha = 0
            customBackView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
            defaultBackButton.alpha = 1
            defaultBackButton.transform = .identity
        } completion: { _ in
            customBackView.removeFromSuperview()
            defaultBackButton.removeFromSuperview()
            // 让下一页使用系统默认返回按钮
            viewController.navigationItem.leftBarButtonItem = nil
            viewController.navigationItem.hidesBackButton = false
        }
    }
    
    // Pop页面时:默认按钮 → 自定义按钮的反向过渡
    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        guard animated, let targetBackView = viewController.navigationItem.leftBarButtonItem?.customView as? BlurBackButtonView else {
            return
        }
        
        // 找到导航栏上的系统默认返回按钮(或用快照替代)
        let defaultBackButton = navigationBar.subviews.first(where: {
            guard let btn = $0 as? UIButton else { return false }
            return btn.imageView?.image == UIImage(systemName: "chevron.left")
        }) as? UIButton
        
        guard let defaultBtn = defaultBackButton else { return }
        
        // 添加自定义按钮到导航栏准备动画
        navigationBar.addSubview(targetBackView)
        targetBackView.alpha = 0
        targetBackView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
        
        // 执行反向动画
        UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
            defaultBtn.alpha = 0
            defaultBtn.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
            targetBackView.alpha = 1
            targetBackView.transform = .identity
        } completion: { _ in
            defaultBtn.removeFromSuperview()
        }
    }
}

3. 在目标页面设置自定义返回按钮

在需要显示自定义模糊返回按钮的ViewController中,替换系统默认按钮:

import UIKit

class CustomBackButtonVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        // 创建自定义返回按钮并设置到导航栏
        let blurBackView = BlurBackButtonView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
        blurBackView.tapHandler = { [weak self] in
            self?.navigationController?.popViewController(animated: true)
        }
        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: blurBackView)
        navigationItem.hidesBackButton = true
    }
}

注意事项

  • 动画时长设置为0.3秒,与系统原生页面过渡动画节奏匹配
  • 若需要更精准的系统默认按钮样式,可使用snapshotView(afterScreenUpdates:)获取系统返回按钮的快照,替代手动创建按钮
  • 横竖屏切换时,需重新调整自定义按钮的frame,避免布局偏移
  • 测试时需覆盖push、pop、侧滑返回等所有导航场景,确保动画连贯

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 10:09:57