You need to enable JavaScript to run this app. Swift 实现同一容器内基于Nib的子视图控制器切换方案
优惠活动
大模型
产品
解决方案
定价
更多

Swift 实现同一容器内基于Nib的子视图控制器切换方案

多Nib子控制器容器切换与交互实现方案

你现有代码的核心逻辑缺陷:

  • 没有打通子控制器与容器的交互链路,容器拿不到子按钮的点击事件
  • 违反UIKit容器控制器规范,提前加载所有子控制器视图、仅手动添加首个子视图,没有维护页面切换的状态
  • 硬访问子控制器内部控件会造成强耦合,长期维护性差

1. 解耦交互:通过代理打通子控制器与容器的通信

不要让容器直接读取子控制器的内部nextButton,定义通用的交互协议,让每个步骤子控制器主动把点击事件抛给容器:

// 定义统一的步骤页交互协议
protocol StepViewControllerDelegate: AnyObject {
    /// 子控制器触发"下一步"事件
    func stepViewControllerDidTapNext(_ stepVC: UIViewController)
    /// 子控制器触发"返回上一步"事件
    func stepViewControllerDidTapBack(_ stepVC: UIViewController)
}

给三个独立的Step子控制器(StepOne/StepTwo/StepThree)统一增加弱引用代理属性,将原有关联在nextButton上的IBAction逻辑替换为抛出代理事件:

class StepOneViewController: UIViewController {
    // 弱引用持有代理,避免循环引用
    weak var delegate: StepViewControllerDelegate?
    
    @IBOutlet private(set) weak var nextButton: UIButton!
    
    // 原有nextButton关联的IBAction
    @IBAction func nextButtonTapped(_ sender: UIButton) {
        delegate?.stepViewControllerDidTapNext(self)
    }
}

硬访问子控制器内部控件的问题:一旦子控制器内部按钮命名调整、样式变更,甚至某一步骤页没有next按钮,容器层代码会直接触发空值崩溃。代理方式下,子控制器内部逻辑完全自闭环,容器不需要关心子控件的实现细节,仅响应约定好的交互事件即可。


2. 补全容器的页面切换核心逻辑

在ContainerViewController中增加状态维护变量,替换原有提前加载所有子视图的逻辑:

class ContainerViewController: UIViewController {
    // 当前展示的步骤索引
    private var currentStepIndex = 0
    // 当前正在展示的子控制器引用
    private var currentDisplayedVC: UIViewController?

    var stepList = [UIViewController]()
    // 原有outlets部分保持不变:logoImageview、titleLabel、progressStackView
}

重写setupControllers方法,完成子控制器初始化、代理绑定、首页面加载:

private func setupControllers() {
    // 初始化三个Nib子控制器
    let step1 = StepOneViewController(nibName: "StepOneViewController", bundle: nil)
    let step2 = StepTwoViewController(nibName: "StepTwoViewController", bundle: nil)
    let step3 = StepThreeViewController(nibName: "StepThreeViewController", bundle: nil)
    
    // 统一为所有子控制器设置代理
    [step1, step2, step3].forEach { $0.delegate = self }
    stepList = [step1, step2, step3]
    
    // 加载首个展示页面
    showStep(at: currentStepIndex)
}

实现通用的页面切换方法,严格遵循UIKit容器控制器的生命周期规范,完全复用你现有的布局约束逻辑:

private func showStep(at index: Int) {
    // 边界判断,防止数组越界
    guard index >= 0, index < stepList.count else { return }
    // 更新当前步骤索引
    currentStepIndex = index
    let targetVC = stepList[index]
    
    // 移除当前正在展示的旧控制器
    if let oldVC = currentDisplayedVC {
        oldVC.willMove(toParent: nil)
        oldVC.view.removeFromSuperview()
        oldVC.removeFromParent()
    }
    
    // 将新控制器加入容器层级
    addChild(targetVC)
    // 复用你原有写好的布局约束逻辑
    configureStepViewsLayout(onStepView: targetVC.view)
    targetVC.didMove(toParent: self)
    
    // 更新当前展示的控制器引用
    currentDisplayedVC = targetVC
    
    // 切页完成后,同步更新进度条样式
    updateProgressStack(for: index)
}

你原有的configureStepViewsLayout方法不需要做任何修改,可以直接复用,因为传入的目标视图就是要展示的子控制器视图,和原有布局逻辑完全兼容。


3. 接收子控制器交互事件,实现切页动作

让ContainerViewController遵守StepViewControllerDelegate协议,实现代理方法,接住子控制器抛出的点击事件:

extension ContainerViewController: StepViewControllerDelegate {
    func stepViewControllerDidTapNext(_ stepVC: UIViewController) {
        // 点击下一步时,索引+1,触发切页
        showStep(at: currentStepIndex + 1)
    }
    
    func stepViewControllerDidTapBack(_ stepVC: UIViewController) {
        // 点击返回时,索引-1,触发切页
        showStep(at: currentStepIndex - 1)
    }
}

4. 实现进度条联动更新

在updateProgressStack方法中,根据当前的步骤索引同步调整progressStackView的样式,这个方法会在每次切页完成后自动触发:

private func updateProgressStack(for stepIndex: Int) {
    // 遍历stackView的每个子视图,对应每个步骤的指示器
    progressStackView.arrangedSubviews.enumerated().forEach { (index, indicatorView) in
        if index <= stepIndex {
            // 已完成/当前所在步骤,设为主题色
            indicatorView.backgroundColor = .systemBlue
        } else {
            // 未到达的步骤,设为浅灰色
            indicatorView.backgroundColor = .lightGray
        }
    }
    
    // 可同步更新公共标题的文案
    titleLabel.text = "第\(stepIndex + 1)步 / 共\(stepList.count)步"
}

关键注意事项

  • 不要提前访问所有子控制器的view属性(即原代码中提前取stepOneView/stepTwoView/stepThreeView的操作),UIKit的控制器视图是懒加载的,仅在需要展示时再触发视图加载,可避免多余视图叠加、内存浪费的问题。
  • 所有子控制器的代理必须用weak修饰,否则会出现“容器持有子控制器、子控制器持有容器代理”的循环引用,造成内存泄漏。
  • 切页时必须完整走容器控制器的生命周期方法(addChild、willMove(toParent:)、removeFromParent、didMove(toParent:)),否则会出现子控制器事件响应异常、内存无法回收、旋转适配失效的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.02 23:24:06