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
相关产品推荐
相关产品推荐

