SwiftUI:如何用多态让模型类生成自身视图并实现状态更新?
SwiftUI 题型统一协议+状态自动更新解决方案
核心思路
让每个题型模型同时承担数据存储(ObservableObject)和视图生成的职责,通过统一协议规范接口,主视图仅需遍历模型数组即可渲染所有题型,新增题型无需修改主视图,完全符合开闭原则。
实现步骤
1. 定义通用题型协议
协议继承ObservableObject,确保模型具备状态可观察能力,同时约定通用属性和视图生成方法:
import SwiftUI import Combine protocol QuestionType: ObservableObject, Identifiable { var id: UUID { get } var isEnabled: Bool { get set } var title: String { get } func makeView() -> AnyView } // 给所有QuestionType默认实现Identifiable的id属性 extension QuestionType { var id: UUID { UUID() } }
2. 实现具体题型模型与视图
每个题型模型实现QuestionType协议,用@Published标记需要触发视图更新的属性,同时返回专属视图:
判断题示例
// 判断题专属视图 struct TrueFalseQuestionView: View { @ObservedObject var model: TrueFalseQuestion var body: some View { HStack { Toggle(model.title, isOn: $model.isEnabled) Spacer() Toggle("正确答案:对", isOn: $model.isCorrectAnswerTrue) .disabled(!model.isEnabled) } .padding(.vertical, 8) } } // 判断题模型 class TrueFalseQuestion: QuestionType { @Published var isEnabled: Bool @Published var isCorrectAnswerTrue: Bool let title: String = "判断题" init(isEnabled: Bool = true, isCorrectAnswerTrue: Bool = true) { self.isEnabled = isEnabled self.isCorrectAnswerTrue = isCorrectAnswerTrue } func makeView() -> AnyView { AnyView(TrueFalseQuestionView(model: self)) } }
填空题示例
// 填空题专属视图 struct FillBlankQuestionView: View { @ObservedObject var model: FillBlankQuestion var body: some View { VStack(alignment: .leading, spacing: 8) { Toggle(model.title, isOn: $model.isEnabled) TextField("请输入正确答案", text: $model.correctAnswer) .textFieldStyle(.roundedBorder) .disabled(!model.isEnabled) } .padding(.vertical, 8) } } // 填空题模型 class FillBlankQuestion: QuestionType { @Published var isEnabled: Bool @Published var correctAnswer: String let title: String = "填空题" init(isEnabled: Bool = true, correctAnswer: String = "") { self.isEnabled = isEnabled self.correctAnswer = correctAnswer } func makeView() -> AnyView { AnyView(FillBlankQuestionView(model: self)) } }
3. 主视图与题型管理
主视图通过QuestionManager管理题型数组,无需判断具体类型,直接遍历调用模型的makeView()方法:
struct QuestionCreatorView: View { @StateObject private var questionManager = QuestionManager() var body: some View { NavigationStack { List { ForEach(questionManager.questions) { question in question.makeView() } .onDelete(perform: questionManager.removeQuestion) HStack(spacing: 16) { Button("添加判断题") { questionManager.addQuestion(TrueFalseQuestion()) } Button("添加填空题") { questionManager.addQuestion(FillBlankQuestion()) } } .padding(.vertical, 8) } .navigationTitle("测试题创建器") .toolbar { EditButton() } } } } // 题型数组管理器 class QuestionManager: ObservableObject { @Published var questions: [any QuestionType] = [] func addQuestion(_ question: any QuestionType) { questions.append(question) } func removeQuestion(at offsets: IndexSet) { questions.remove(atOffsets: offsets) } }
关键说明
- 状态自动更新:每个模型都是
ObservableObject,@Published标记的属性变化会自动通知关联视图刷新,解决了你之前遇到的视图不更新问题。 - 开闭原则:新增题型只需实现
QuestionType协议,主视图和管理器无需任何修改。 - 解耦设计:主视图无需关心具体题型类型,仅依赖统一协议,完全解耦了视图与具体业务逻辑。
内容的提问来源于stack exchange,提问作者Felix Uff
相关产品推荐
相关产品推荐

