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

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) {
        // 音频播放逻辑
    }
}

问题根源

  1. correctAnswer初始化时机错误:@State变量在结构体创建时生成随机值,返回视图时不会重新初始化,导致第一题的正确答案沿用旧值。
  2. 选项逻辑固定:直接取数组前三个元素作为选项,未和正确答案关联,也未生成包含正确答案的随机选项组。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:40:54