UIKit实现多视图控制器间共享带动画进度条的最优方案
UIKit 实现多视图共享带动画进度条的最优方案
针对你4步注册表单的场景,容器控制器(Container View Controller)统一管理进度条与步骤控制器是当前UIKit中最简洁合理的方案,比自定义导航栏或零散子控制器方案更具可维护性与扩展性,也符合现代UIKit的最佳实践。
核心实现思路
创建进度容器父控制器
专门搭建一个父控制器(比如RegistrationProgressContainerVC),作为整个注册流程的入口:- 顶部固定嵌入自定义的带动画进度条视图
- 下方用容器视图承载各个注册步骤的子控制器
- 由父控制器统一维护当前步骤索引、进度状态,以及步骤切换逻辑
封装进度条动画逻辑
把进度条的绘制、动画逻辑封装在独立的AnimatedProgressBar视图中,对外只暴露更新进度的接口,避免业务逻辑耦合:- 支持根据总步骤数计算进度比例
- 内置平滑过渡动画,可通过参数控制是否启用动画
- 适配布局变化,防止进度条错位
步骤控制器解耦
每个注册步骤的控制器只负责自身表单逻辑,通过闭包/代理通知父控制器切换步骤,无需关心进度条的状态更新。
方案对比:自定义导航栏 vs 容器控制器
自定义导航栏方案
- 优点:利用系统导航栏的全局存在性,无需额外搭建容器结构
- 缺点:
- 导航栏布局限制多,进度条的位置、动画容易与系统导航栏的原生交互(大标题、返回按钮等)冲突
- 若注册流程不需要导航栏的其他功能,会造成冗余
- iOS版本迭代可能带来兼容性问题,适配成本高
容器控制器方案
- 优点:
- 进度条状态统一管理,避免多视图间的状态同步问题
- 步骤控制器与进度逻辑完全解耦,代码可维护性强
- 布局灵活,可自由调整进度条的位置、样式,不受系统组件限制
- 缺点:需要额外创建一个父控制器,但整体代码复杂度更低
关键代码示例
自定义带动画进度条
class AnimatedProgressBar: UIView { private let progressLayer = CAShapeLayer() private let totalSteps: Int init(totalSteps: Int) { self.totalSteps = totalSteps super.init(frame: .zero) setupBaseLayer() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupBaseLayer() { // 背景层 let backgroundLayer = CAShapeLayer() backgroundLayer.frame = bounds backgroundLayer.fillColor = UIColor.systemGray5.cgColor layer.addSublayer(backgroundLayer) // 进度层 progressLayer.fillColor = UIColor.systemBlue.cgColor progressLayer.frame = CGRect(x: 0, y: 0, width: 0, height: bounds.height) layer.addSublayer(progressLayer) } func updateProgress(to currentStep: Int, animated: Bool) { let progressRatio = CGFloat(currentStep) / CGFloat(totalSteps) let targetWidth = bounds.width * progressRatio let targetFrame = CGRect(x: 0, y: 0, width: targetWidth, height: bounds.height) if animated { let animation = CABasicAnimation(keyPath: "frame") animation.fromValue = progressLayer.frame animation.toValue = targetFrame animation.duration = 0.3 animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) progressLayer.add(animation, forKey: "progressAnimation") } progressLayer.frame = targetFrame } override func layoutSubviews() { super.layoutSubviews() // 布局变化时重新校准进度 let currentProgress = progressLayer.frame.width / bounds.width progressLayer.frame = CGRect(x: 0, y: 0, width: bounds.width * currentProgress, height: bounds.height) layer.sublayers?.first?.frame = bounds } }
进度容器父控制器核心逻辑
class RegistrationProgressContainerVC: UIViewController { private let totalSteps = 4 private var currentStep = 0 private let progressBar = AnimatedProgressBar(totalSteps: 4) private let containerView = UIView() override func viewDidLoad() { super.viewDidLoad() setupLayout() switchToStep(0) } private func setupLayout() { // 进度条布局 view.addSubview(progressBar) progressBar.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ progressBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), progressBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), progressBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), progressBar.heightAnchor.constraint(equalToConstant: 4) ]) // 步骤容器布局 view.addSubview(containerView) containerView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ containerView.topAnchor.constraint(equalTo: progressBar.bottomAnchor, constant: 24), containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } func switchToStep(_ step: Int) { guard step >= 0 && step < totalSteps else { return } // 移除当前子控制器 children.forEach { child in child.willMove(toParent: nil) child.view.removeFromSuperview() child.removeFromParent() } // 创建对应步骤控制器 let stepVC: UIViewController switch step { case 0: stepVC = RegistrationStep1VC(onNext: { [weak self] in self?.switchToStep(1) }) case 1: stepVC = RegistrationStep2VC(onNext: { [weak self] in self?.switchToStep(2) }, onPrevious: { [weak self] in self?.switchToStep(0) }) case 2: stepVC = RegistrationStep3VC(onNext: { [weak self] in self?.switchToStep(3) }, onPrevious: { [weak self] in self?.switchToStep(1) }) case 3: stepVC = RegistrationStep4VC(onComplete: { [weak self] in // 注册完成逻辑 }, onPrevious: { [weak self] in self?.switchToStep(2) }) default: stepVC = UIViewController() } // 添加新子控制器 addChild(stepVC) containerView.addSubview(stepVC.view) stepVC.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ stepVC.view.topAnchor.constraint(equalTo: containerView.topAnchor), stepVC.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), stepVC.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), stepVC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) ]) stepVC.didMove(toParent: self) // 更新进度条 currentStep = step progressBar.updateProgress(to: step + 1, animated: true) } }
额外优化建议
- 可以配合
UIViewControllerTransitionCoordinator,让步骤切换的过渡动画与进度条动画同步,提升交互流畅度 - 进度条的动画效果可根据需求扩展,比如添加渐变、圆角动画,或用
UIBezierPath实现分段式进度显示
内容的提问来源于stack exchange,提问作者Daniel Ramos
相关产品推荐
相关产品推荐

