Swift中如何实现带TextField输入的通用匹配验证及动态卡片添加?
需求说明
我需要实现以下功能:
- 制作一个带通用函数的按钮,该函数能从数组中随机选取元素,检查TextField的输入是否匹配该元素对应的正确内容,匹配时返回true
- 将现有代码改造为通用实现,支持常量、整数等多种类型(不局限于变量)
- 支持通过按钮向视图中添加更多任务卡片
现有代码如下:
//MARK: LOGIC import Foundation import Observation @Observable class VariablesViewModel { var textField = "" private var words = ["\"Hello\"","\"Hello \"" ,"\"hello\"", "\" Hello\"", "\" hello\""] func wordMatch() -> Bool { words.contains(where: textField.contains) } } //MARK: VIEW import SwiftUI struct Variables: View { @State var viewModel = VariablesViewModel() var body: some View { VStack(alignment: .leading, spacing: 40) { Text("Create a variable called \"greeting\" and assign the value \"Hello\".") .font(.title2) .fontWeight(.semibold) HStack { Text("1") .font(.title2) .foregroundStyle(.gray) Text("var") .font(.title2) .foregroundStyle(.pink) Text("greeting =") .font(.title2) TextField(text: $viewModel.textField ) { Text("Tap to assign \"Hello\"") .font(.title2) } .font(.title2) .foregroundStyle(.orange) if viewModel.wordMatch() { Image(systemName: "checkmark.circle.fill") .imageScale(.large) .foregroundStyle(.green) } } } .alignment(.topLeading) } } #Preview { Variables() } extension View { @ViewBuilder func alignment(_ alignment: Alignment) -> some View { self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment) } }
解决方案
实现思路
- 定义通用任务结构:用结构体封装不同类型的任务,包含任务描述、正确答案集合、任务类型标识,覆盖变量、常量、整数等场景
- 通用ViewModel改造:支持管理多任务列表,实现随机选任务、添加新任务、通用输入匹配检查的核心逻辑
- 动态视图适配:让视图根据ViewModel中的任务列表动态渲染卡片,添加按钮触发任务添加操作
完整代码实现
1. 通用任务模型与ViewModel
import Foundation import Observation // 任务类型枚举,区分变量、常量、整数场景 enum TaskType: String { case variable = "var" case constant = "let" case integer = "integer" } // 通用任务结构体,封装任务的所有核心信息 struct Task { let id: UUID let description: String let correctAnswers: [String] let type: TaskType var inputText: String = "" // 存储当前任务的用户输入 } @Observable class GenericTaskViewModel { var tasks: [Task] = [] init() { // 初始化默认任务 let initialTask = Task( id: UUID(), description: "Create a variable called \"greeting\" and assign the value \"Hello\".", correctAnswers: ["\"Hello\"", "\"Hello \"", "\"hello\"", "\" Hello\"", "\" hello\""], type: .variable ) tasks.append(initialTask) } // 通用输入匹配检查:判断当前输入是否匹配任务的任一正确答案 func isInputCorrect(for task: Task) -> Bool { task.correctAnswers.contains(where: task.inputText.contains) } // 添加随机任务:从预设任务库中随机选一个添加到列表 func addRandomTask() { let presetTasks: [Task] = [ Task( id: UUID(), description: "Create a constant called \"maxScore\" and assign the value 100.", correctAnswers: ["100", " 100", "100 "], type: .constant ), Task( id: UUID(), description: "Create an integer variable called \"age\" and assign the value 25.", correctAnswers: ["25", " 25", "25 "], type: .integer ), Task( id: UUID(), description: "Create a variable called \"name\" and assign the value \"Alice\".", correctAnswers: ["\"Alice\"", "\"Alice \"", "\"alice\"", "\" Alice\""], type: .variable ) ] guard let randomTask = presetTasks.randomElement() else { return } tasks.append(randomTask) } // 更新指定任务的输入内容 func updateInput(for taskId: UUID, newText: String) { guard let index = tasks.firstIndex(where: { $0.id == taskId }) else { return } tasks[index].inputText = newText } }
2. 通用视图实现
import SwiftUI struct GenericTaskView: View { @State var viewModel = GenericTaskViewModel() var body: some View { VStack(alignment: .leading, spacing: 30) { // 添加任务按钮 Button(action: viewModel.addRandomTask) { Text("Add New Task") .font(.title3) .fontWeight(.semibold) .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } // 动态渲染所有任务卡片 ForEach($viewModel.tasks) { $task in TaskCard(task: $task, isCorrect: viewModel.isInputCorrect(for: task)) } } .padding() .alignment(.topLeading) } } // 单个任务卡片视图 struct TaskCard: View { @Binding var task: Task let isCorrect: Bool var body: some View { VStack(alignment: .leading, spacing: 15) { Text(task.description) .font(.title2) .fontWeight(.semibold) HStack(alignment: .center, spacing: 10) { // 简化的任务序号(取UUID首字符) Text("\(task.id.uuidString.prefix(1))") .font(.title2) .foregroundStyle(.gray) // 根据任务类型显示关键字 if task.type != .integer { Text(task.type.rawValue) .font(.title2) .foregroundStyle(task.type == .variable ? .pink : .purple) } // 根据任务类型显示对应的前缀文本 switch task.type { case .variable: Text("greeting =") .font(.title2) case .constant: Text("maxScore =") .font(.title2) case .integer: Text("var age =") .font(.title2) } TextField( text: $task.inputText, prompt: Text(getPlaceholder(for: task.type)) .font(.title2) ) { EmptyView() } .font(.title2) .foregroundStyle(.orange) // 匹配成功时显示对勾 if isCorrect { Image(systemName: "checkmark.circle.fill") .imageScale(.large) .foregroundStyle(.green) } } .padding(.bottom, 10) .border(.gray.opacity(0.3)) .padding(.horizontal, 5) } } // 根据任务类型返回对应的占位提示文本 private func getPlaceholder(for type: TaskType) -> String { switch type { case .variable: return "Tap to assign \"Hello\"" case .constant: return "Tap to assign 100" case .integer: return "Tap to assign 25" } } } #Preview { GenericTaskView() } extension View { @ViewBuilder func alignment(_ alignment: Alignment) -> some View { self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment) } }
关键说明
- 通用任务结构:
Task结构体通过TaskType枚举适配不同任务场景,每个任务独立维护自己的正确答案集合,扩展性强 - ViewModel通用化:
GenericTaskViewModel统一管理任务列表,核心逻辑与任务类型解耦,支持随机添加任务、通用输入检查 - 动态视图:通过
ForEach($viewModel.tasks)实现任务卡片的动态渲染,每个卡片根据任务类型自动调整显示内容,匹配成功时实时反馈
内容的提问来源于stack exchange,提问作者NodlesView
相关产品推荐
相关产品推荐

