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

iOS 基于服务端JSON动态渲染表单并收集提交答案的方案咨询

实现方案

核心思路是放弃维护视图引用的思路,改用数据驱动+状态回调的方案实现答案同步,既避免视图引用带来的内存风险、复用bug,也更方便后续扩展和维护。


第一步:定义统一的问题数据结构

所有题型共用一套数据模型,把用户答案存在数据层而非视图层,每个问题必须带唯一标识用于区分:

// 题型枚举
enum QuestionType: Int, Codable {
    case textInput
    case radio
    case checkbox
    case dropdown
}

// 选项通用结构
struct OptionModel: Codable {
    let optionId: String
    let text: String
}

// 问题通用结构
struct QuestionModel: Codable {
    let questionId: String // 全局唯一标识,必填
    let type: QuestionType
    let title: String
    let placeholder: String?
    let validateRules: [String: Any]? // 输入校验规则,可按需扩展
    let options: [OptionModel]? // 单选/多选/下拉的选项集
    // 可变字段,存储用户答案
    var userAnswer: Any? // 可存储字符串、选中项ID、选中ID数组等,和题型对应
}

第二步:给自定义复用视图加统一协议

所有题型的自定义视图都遵守同一协议,内部自行处理用户输入的监听,通过回调把最新状态同步给上层,不需要上层额外监听组件事件:

protocol QuestionViewProtocol: UIView {
    // 渲染视图的统一配置方法
    func config(with model: QuestionModel)
    // 用户操作后的回调,向外抛出更新后的问题模型
    var valueUpdateHandler: ((QuestionModel) -> Void)? { get set }
}

不同题型的视图单独实现协议即可:

  • 文本输入视图:监听UITextField的输入事件,更新模型的userAnswer后触发回调
  • 单选/多选视图:监听按钮点击事件,把选中项ID/ID数组赋值给userAnswer后触发回调
  • 下拉视图:选中选项后更新userAnswer触发回调

第三步:上层页面仅维护数据源数组,无需存储视图引用

上层页面只需要维护[QuestionModel]类型的数据源数组,所有答案的更新、校验、提交都基于这个数组操作:

class DynamicFormViewController: UIViewController {
    // 数据源数组,所有用户输入的答案最终都存在这里
    private var questionList: [QuestionModel] = []
    @IBOutlet weak var containerStackView: UIStackView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 从本地数据库读取当前屏幕对应的问题列表
        questionList = LocalDB.shared.fetchQuestions(screenId: 当前屏幕标识)
        // 渲染表单
        renderFormViews()
    }
    
    private func renderFormViews() {
        // 先清空旧视图
        containerStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
        // 遍历数据源生成对应题型的视图
        for (index, model) in questionList.enumerated() {
            let questionView: QuestionViewProtocol
            switch model.type {
            case .textInput:
                questionView = TextInputQuestionView()
            case .radio:
                questionView = RadioQuestionView()
            case .checkbox:
                questionView = CheckboxQuestionView()
            case .dropdown:
                questionView = DropdownQuestionView()
            }
            // 配置视图
            questionView.config(with: model)
            // 绑定值更新回调
            questionView.valueUpdateHandler = { [weak self] updatedModel in
                guard let self = self else { return }
                // 替换数据源中对应位置的旧模型
                self.questionList[index] = updatedModel
            }
            containerStackView.addArrangedSubview(questionView)
            // 按需添加高度、边距约束即可
        }
    }
    
    // 提交表单逻辑
    private func submitAction() {
        // 1. 全局校验输入合法性
        for model in questionList {
            if !checkValidate(model) { // 基于model的validateRules校验userAnswer
                showErrorToast("「\(model.title)」输入不符合要求")
                return
            }
        }
        // 2. 组装提交参数
        var submitParams: [[String: Any]] = []
        for model in questionList {
            submitParams.append([
                "questionId": model.questionId,
                "answer": model.userAnswer ?? NSNull()
            ])
        }
        // 3. 调用提交接口即可
    }
}

可选优化

如果项目使用RxSwift/Combine等响应式框架,可以把questionList改成可观测序列,省略手动回调的代码,绑定逻辑会更简洁。如果需要支持草稿保存,直接把questionList序列化存入本地,下次进入直接读取就能自动回填所有用户输入。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 03:48:00