如何在UINavigationBar中实现自定义返回按钮到默认按钮的动画过渡?
实现自定义返回按钮到默认按钮的过渡动画方案
针对你要复刻App Store/Apple Music的返回按钮过渡效果需求,由于自定义按钮带有视觉模糊效果(VisualEffect),无法通过系统backIndicatorImage系列属性实现,这里给出基于自定义leftBarButtonItem+手动控制过渡动画的可行方案:
核心思路
放弃系统自带的返回按钮样式管理,自己接管导航栈切换时的按钮替换逻辑:
- 在需要自定义返回按钮的页面,设置带视觉效果的自定义视图作为
leftBarButtonItem - 利用导航控制器的
push/pop生命周期钩子,在页面切换时触发自定义按钮与默认返回按钮的过渡动画 - 通过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
相关产品推荐
相关产品推荐

