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

Swift中TableViewCell无法调用父ViewController方法的问题

Swift中MultipleAnswerTableViewCell调用父控制器方法触发nil错误的解决

问题场景

开发中MultipleAnswerTableViewCell调用父控制器AddQuestionViewController的savedAnswer方法时,触发致命错误:Unexpectedly found nil while implicitly unwrapping an Optional value。已在父控制器的configureMultiAnswerCell方法中为cell的parentVC属性赋值self,但问题依旧,怀疑saveButtonTapped方法内的重新dequeueCell操作是诱因。

核心代码片段

MultipleAnswerTableViewCell关键代码

class MultipleAnswerTableViewCell: UITableViewCell, SurveyAnswerViewDelegate, UITextFieldDelegate {
    // 隐式可选的父控制器引用
    var parentVC: AddQuestionViewController!

    // 触发错误的代码
    let surveyAnswer = SurveyAnswer(answer: textField.text!, order: Int(textField.tag))
    surveyAnswers.append(surveyAnswer)
    self.parentVC?.savedAnswer(answer: surveyAnswer) // 此处触发nil解包错误

    // 尝试可选绑定但仍无效
    if let myParentVC = self.parentVC as? AddQuestionViewController {
        myParentVC.savedAnswer(answer: surveyAnswer)
    }
}

AddQuestionViewController关键代码

class AddQuestionViewController: BaseViewController, SurveyTopTableViewCellDelegate, MultipleAnswerTableViewCellDelegate, SurveyTextAnswerTableViewCellDelegate, UITextFieldDelegate {

    func configureMultiAnswerCell(indexPath: IndexPath) -> MultipleAnswerTableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MultipleAnswerTableViewCell") as! MultipleAnswerTableViewCell
        cell.parentVC = self
        return cell
    }

    func savedAnswer(answer: SurveyAnswer) {
        self.surveyAnswers.append(answer)
        print("answerLo", answer.answer)
    }

    // TableView数据源方法
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch cellTypes[indexPath.row] {
        case .fixedTop: return configureFixedTop(indexPath: indexPath)
        case .multiAnswer: return configureMultiAnswerCell(indexPath: indexPath)
        }
    }

    // 可疑的保存按钮点击方法
    @IBAction func saveButtonTapped(_ sender: UIButton) {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MultipleAnswerTableViewCell") as! MultipleAnswerTableViewCell
        cell.mainSaveTapped(stackView: savedStack)
    }
}

问题根源

  1. saveButtonTapped方法的错误操作:调用tableView.dequeueReusableCell会创建一个全新的MultipleAnswerTableViewCell实例,这个新实例从未被赋值parentVC,因此调用mainSaveTapped时,Cell内的parentVC为nil,触发隐式解包错误。
  2. 隐式可选类型的风险:var parentVC: AddQuestionViewController!使用隐式可选,一旦值为nil就会直接崩溃,即使后续用可选绑定也无法提前规避实例未初始化的问题。
  3. 强引用的潜在循环风险:直接持有父控制器的强引用,可能引发循环引用导致内存泄漏。

解决方案

方案1:修复保存按钮获取Cell的方式

不要重新创建Cell,而是获取当前TableView中已显示的Cell实例:

@IBAction func saveButtonTapped(_ sender: UIButton) {
    // 根据cellTypes中multiAnswer的位置,获取对应的indexPath
    guard let row = cellTypes.firstIndex(of: .multiAnswer) else { return }
    let indexPath = IndexPath(row: row, section: 0)
    if let cell = tableView.cellForRow(at: indexPath) as? MultipleAnswerTableViewCell {
        cell.mainSaveTapped(stackView: savedStack)
    }
}

方案2:修改Cell的父控制器引用为可选类型

将隐式可选改为普通可选,避免强制解包崩溃:

class MultipleAnswerTableViewCell: UITableViewCell, SurveyAnswerViewDelegate, UITextFieldDelegate {
    // 改为可选类型
    var parentVC: AddQuestionViewController?

    // 调用方法时增加安全判断
    guard let text = textField.text, 
          let order = Int(textField.tag),
          let parentVC = parentVC else { return }
    let surveyAnswer = SurveyAnswer(answer: text, order: order)
    surveyAnswers.append(surveyAnswer)
    parentVC.savedAnswer(answer: surveyAnswer)
}

方案3:使用代理模式(推荐)

利用已定义的MultipleAnswerTableViewCellDelegate,避免直接持有父控制器引用,消除循环引用风险:

1. 完善Cell的代理协议

// 确保代理使用weak避免循环引用
protocol MultipleAnswerTableViewCellDelegate: AnyObject {
    func multipleAnswerCell(_ cell: MultipleAnswerTableViewCell, didSave answer: SurveyAnswer)
}

class MultipleAnswerTableViewCell: UITableViewCell, SurveyAnswerViewDelegate, UITextFieldDelegate {
    weak var delegate: MultipleAnswerTableViewCellDelegate?

    // 触发保存时调用代理方法
    guard let text = textField.text, let order = Int(textField.tag) else { return }
    let surveyAnswer = SurveyAnswer(answer: text, order: order)
    surveyAnswers.append(surveyAnswer)
    delegate?.multipleAnswerCell(self, didSave: surveyAnswer)
}

2. 父控制器实现代理并绑定

class AddQuestionViewController: BaseViewController, ..., MultipleAnswerTableViewCellDelegate {

    func configureMultiAnswerCell(indexPath: IndexPath) -> MultipleAnswerTableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MultipleAnswerTableViewCell") as! MultipleAnswerTableViewCell
        cell.delegate = self // 替换parentVC赋值为代理绑定
        return cell
    }

    // 实现代理方法
    func multipleAnswerCell(_ cell: MultipleAnswerTableViewCell, didSave answer: SurveyAnswer) {
        self.surveyAnswers.append(answer)
        print("answerLo", answer.answer)
    }
}

内容的提问来源于stack exchange,提问作者Mert Köksal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 21:30:51