SwiftUI问题:返回视图时首题随机选项模式始终相同如何解决?
日语假名自测项目:修复第一题选项不随机问题
问题描述
开发日语假名自测项目,功能为点击按钮播放发音后从三个选项中选择正确假名,共10道题。需求是每次进入视图时所有题目均随机,但目前返回视图时第一题的选项模式始终固定,后续题目才会随机,需实现返回视图时第一题选项也随机。
原代码
import SwiftUI struct HiraganaQuiz: View { var hiraganas: [Japanese] = Bundle.main.decode("Hiragana.json") @State private var correctAnswer = Int.random(in: 0...45) @StateObject var soundplayer = Audio() @State private var answer = "" @State private var counter = 0 @State private var correctAnswerCounter = 0 @State private var showingAlert = false @State private var alertMessage = "" @State private var disabled = false var body: some View { ZStack { Color.darkBackground .ignoresSafeArea() VStack { Text("\(counter) / 10") .padding(.top,40) .foregroundColor(.white) .font(.system(size:30).bold()) Text("Tap the speaker and choose the right letter") Button { soundplayer.playSounds(file: hiraganas[correctAnswer].voice1) } label: { Image(systemName: "speaker.wave.3.fill") } .font(.system(size:70)) // 原代码此处存在语法错误,已移除多余内容 HStack { ForEach (0...2, id: \.self) { index in Button { letterTapped(index) } label: { Text(hiraganas[index].letter) } } .disabled(disabled) // 修正原代码函数名错误 .foregroundColor(.white) .font(.system(size: 35).bold()) } Text("\(answer)") .foregroundColor(.white) .padding(.bottom,20) .font(.system(size: 30)) Button { resetTheGame() } label: { Text("Next") }.buttonStyle(.plain) .font(.system(size: 30).bold()) .frame(width: 200, height: 50) } } .alert("⭐️ Well done ⭐️", isPresented: $showingAlert) { Button("Retry", action: reset) } message: { Text(alertMessage) } .onAppear { hiraganas = Bundle.main.decode("Hiragana.json").shuffled() } } // 补充原代码缺失的基础函数实现 func letterTapped(_ index: Int) { if index == correctAnswer { answer = "Correct!" correctAnswerCounter += 1 } else { answer = "Wrong! Correct is \(hiraganas[correctAnswer].letter)" } disabled = true } func resetTheGame() { if counter < 9 { counter += 1 correctAnswer = Int.random(in: 0...45) disabled = false answer = "" } else { alertMessage = "You got \(correctAnswerCounter)/10 correct!" showingAlert = true } } func reset() { counter = 0 correctAnswerCounter = 0 correctAnswer = Int.random(in: 0...45) disabled = false answer = "" hiraganas = Bundle.main.decode("Hiragana.json").shuffled() } func disabled() -> Bool { return disabled } } // 假设的Japanese结构体定义 struct Japanese: Codable { let letter: String let voice1: String } // 假设的Color扩展 extension Color { static let darkBackground = Color.black } // 假设的Audio类定义 class Audio: ObservableObject { func playSounds(file: String) { // 音频播放逻辑 } }
问题根源
correctAnswer初始化时机错误:@State变量在结构体创建时生成随机值,返回视图时不会重新初始化,导致第一题的正确答案沿用旧值。- 选项逻辑固定:直接取数组前三个元素作为选项,未和正确答案关联,也未生成包含正确答案的随机选项组。
onAppear未重置完整状态:仅打乱数组,未重新生成正确答案和选项,也未重置计数器等状态。
解决方案
1. 新增状态变量存储当前选项组
添加@State private var currentOptions: [Japanese] = [],用于存储每道题的三个随机选项(包含正确答案)。
2. 编写生成随机选项组的方法
实现方法随机选择正确答案,并生成包含该答案的三个不重复选项:
func generateQuestion() { // 随机选择正确答案 let correctHiragana = hiraganas.randomElement()! correctAnswer = hiraganas.firstIndex(of: correctHiragana)! // 生成包含正确答案的三个不重复选项 var options = [correctHiragana] while options.count < 3 { if let randomHiragana = hiraganas.randomElement(), !options.contains(randomHiragana) { options.append(randomHiragana) } } // 打乱选项顺序,避免正确答案位置固定 currentOptions = options.shuffled() }
3. 修改onAppear方法
每次视图出现时,重置所有状态并生成第一题:
.onAppear { hiraganas = Bundle.main.decode("Hiragana.json").shuffled() counter = 0 correctAnswerCounter = 0 disabled = false answer = "" generateQuestion() }
4. 修正选项显示逻辑
遍历currentOptions显示选项,而非固定索引:
HStack { ForEach(currentOptions.indices, id: \.self) { index in Button { letterTapped(index) } label: { Text(currentOptions[index].letter) } } .disabled(disabled) .foregroundColor(.white) .font(.system(size: 35).bold()) }
5. 更新letterTapped方法
判断点击的选项是否为正确答案:
func letterTapped(_ index: Int) { let selected = currentOptions[index] let correct = hiraganas[correctAnswer] if selected == correct { answer = "正确!" correctAnswerCounter += 1 } else { answer = "错误!正确答案是 \(correct.letter)" } disabled = true }
6. 修正resetTheGame和reset方法
确保每次进入下一题或重试时重新生成选项组:
func resetTheGame() { if counter < 9 { counter += 1 disabled = false answer = "" generateQuestion() } else { alertMessage = "你答对了 \(correctAnswerCounter)/10 题!" showingAlert = true } } func reset() { counter = 0 correctAnswerCounter = 0 disabled = false answer = "" hiraganas = Bundle.main.decode("Hiragana.json").shuffled() generateQuestion() }
修改后的完整代码
import SwiftUI struct HiraganaQuiz: View { var hiraganas: [Japanese] = Bundle.main.decode("Hiragana.json") @State private var correctAnswer = 0 @State private var currentOptions: [Japanese] = [] @StateObject var soundplayer = Audio() @State private var answer = "" @State private var counter = 0 @State private var correctAnswerCounter = 0 @State private var showingAlert = false @State private var alertMessage = "" @State private var disabled = false var body: some View { ZStack { Color.darkBackground .ignoresSafeArea() VStack { Text("\(counter + 1) / 10") // 修正计数器显示,从1开始 .padding(.top,40) .foregroundColor(.white) .font(.system(size:30).bold()) Text("点击喇叭,选择正确的假名") Button { soundplayer.playSounds(file: hiraganas[correctAnswer].voice1) } label: { Image(systemName: "speaker.wave.3.fill") } .font(.system(size:70)) .foregroundColor(.white) HStack { ForEach(currentOptions.indices, id: \.self) { index in Button { letterTapped(index) } label: { Text(currentOptions[index].letter) } } .disabled(disabled) .foregroundColor(.white) .font(.system(size: 35).bold()) } .padding(.vertical, 20) Text("\(answer)") .foregroundColor(.white) .padding(.bottom,20) .font(.system(size: 30)) Button { resetTheGame() } label: { Text("下一题") } .buttonStyle(.plain) .font(.system(size: 30).bold()) .frame(width: 200, height: 50) .background(Color.blue) .cornerRadius(10) } } .alert("⭐️ 完成测试 ⭐️", isPresented: $showingAlert) { Button("重新开始", action: reset) } message: { Text(alertMessage) } .onAppear { hiraganas = Bundle.main.decode("Hiragana.json").shuffled() counter = 0 correctAnswerCounter = 0 disabled = false answer = "" generateQuestion() } } func generateQuestion() { let correctHiragana = hiraganas.randomElement()! correctAnswer = hiraganas.firstIndex(of: correctHiragana)! var options = [correctHiragana] while options.count < 3 { if let randomHiragana = hiraganas.randomElement(), !options.contains(randomHiragana) { options.append(randomHiragana) } } currentOptions = options.shuffled() } func letterTapped(_ index: Int) { let selected = currentOptions[index] let correct = hiraganas[correctAnswer] if selected == correct { answer = "正确!" correctAnswerCounter += 1 } else { answer = "错误!正确答案是 \(correct.letter)" } disabled = true } func resetTheGame() { if counter < 9 { counter += 1 disabled = false answer = "" generateQuestion() } else { alertMessage = "你答对了 \(correctAnswerCounter)/10 题!" showingAlert = true } } func reset() { counter = 0 correctAnswerCounter = 0 disabled = false answer = "" hiraganas = Bundle.main.decode("Hiragana.json").shuffled() generateQuestion() } } struct Japanese: Codable, Equatable { // 添加Equatable协议用于判断选项是否相等 let letter: String let voice1: String } extension Color { static let darkBackground = Color(red: 0.1, green: 0.1, blue: 0.1) } class Audio: ObservableObject { func playSounds(file: String) { // 此处添加音频播放逻辑,例如使用AVFoundation } } struct HiraganaQuiz_Previews: PreviewProvider { static var previews: some View { HiraganaQuiz() } }
内容的提问来源于stack exchange,提问作者Schocolate
相关产品推荐
相关产品推荐

