SwiftUI MVVM问答游戏:地理/体育题目无法加载问题求助
问答游戏类别加载问题修复方案
问题根源排查
- 题目存储逻辑错误:
GameManagerVM的fetchQuiz方法循环请求三个类别接口,但每次请求完成后都用新数据覆盖quiz数组,最终仅保留最后一个请求(历史类)的题目,导致地理、体育类视图无法获取对应题目。 - 类别切换逻辑缺失:点击不同类别按钮时,没有通知
GameManagerVM切换到对应类别的题目集合,始终加载初始的历史类题目。 - 视图类别判断依赖单一状态:地理、体育视图通过判断
gameManager.category是否匹配来显示内容,但由于quiz数组只有历史类题目,category永远为"History",导致其他视图的内容不显示。
修复步骤
1. 修改GameManagerVM的存储结构与逻辑
更新GameManagerVM,增加按类别存储题目的字典,并添加切换类别的方法:
import Foundation import SwiftUI class GameManagerVM: ObservableObject { // 按类别存储题目 private (set) var categoryQuizzes: [String: [Quiz.Result]] = [:] private (set) var currentQuiz: [Quiz.Result] = [] @Published private(set) var lenght = 0 @Published private(set) var index = 0 @Published private(set) var reachedEnd = false @Published private(set) var answerIsSelected = false @Published private(set) var question: AttributedString = "" @Published private(set) var answerChoices: [Answer] = [] @Published private(set) var progress: CGFloat = 0.00 @Published private(set) var score = 0 @Published private(set) var currentCategory = "" init() { Task.init { await fetchQuiz() } } func fetchQuiz() async { let categoryConfig = [ "Sports": "https://opentdb.com/api.php?amount=50&category=21&difficulty=medium&type=multiple", "Geography": "https://opentdb.com/api.php?amount=50&category=22&difficulty=medium&type=multiple", "History": "https://opentdb.com/api.php?amount=50&category=23&difficulty=medium&type=multiple" ] for (category, urlStr) in categoryConfig { guard let url = URL(string: urlStr) else { fatalError("Invalid URL: \(urlStr)") } do { let urlRequest = URLRequest(url: url) let (data, response) = try await URLSession.shared.data(for: urlRequest) guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let decodedData = try decoder.decode(Quiz.self, from: data) DispatchQueue.main.async { self.categoryQuizzes[category] = decodedData.results } } catch { print("Error fetching trivia: \(error)") } } } // 切换到指定类别 func switchToCategory(_ category: String) { guard let quiz = categoryQuizzes[category] else { return } currentQuiz = quiz lenght = currentQuiz.count index = 0 reachedEnd = false score = 0 setQuestion() } func nextQuestion() { if index + 1 < lenght { index += 1 setQuestion() } else { reachedEnd = true } } func setQuestion() { answerIsSelected = false progress = CGFloat(Double(index + 1) / Double(lenght) * 350) if index < lenght { let currentTriviaQuestion = currentQuiz[index] question = currentTriviaQuestion.formattedQuestion answerChoices = currentTriviaQuestion.answers currentCategory = currentTriviaQuestion.category } } func selectAnswer(answer: Answer) { answerIsSelected = true if answer.isCorrect { score += 1 } } }
2. 更新ContentView,传递类别并触发切换
修改ContentView中的NavigationLink,在跳转时调用switchToCategory方法:
import SwiftUI struct ContentView: View { @StateObject var gameManager = GameManagerVM() var body: some View { NavigationView { ZStack { LinearGradient(gradient: .init(colors: [.mint,.indigo]), startPoint: .zero, endPoint: .center) .ignoresSafeArea() VStack(spacing:250) { Text("QUIZ GAME") .font(.system(size: 50)) .bold() .foregroundColor(.white) .shadow(radius: 5) VStack { VStack(spacing: 20) { NavigationLink { HistoryQuizView() .navigationBarHidden(true) .environmentObject(gameManager) } label: { CategoryButton(text:"History") } .onTapGesture { gameManager.switchToCategory("History") } NavigationLink { GeographyQuizView() .navigationBarHidden(true) .environmentObject(gameManager) } label: { CategoryButton(text: "Geography") } .onTapGesture { gameManager.switchToCategory("Geography") } NavigationLink { SportQuizView() .navigationBarHidden(true) .environmentObject(gameManager) } label: { CategoryButton(text:"Sport") } .onTapGesture { gameManager.switchToCategory("Sports") } } } .frame(width: 350, height: 400) .background(.ultraThinMaterial) .cornerRadius(40) } } } } }
3. 修正各视图的类别判断逻辑
- GeographyQuizView:保持判断
gameManager.currentCategory == "Geography"不变(与接口返回的类别名称一致)。 - SportQuizView:修正判断条件为
gameManager.currentCategory == "Sports"(接口返回的类别名称是"Sports",需匹配接口返回值)。 - HistoryQuizView:保持判断
gameManager.currentCategory == "History"不变。
以SportQuizView为例:
import SwiftUI struct SportQuizView: View { @EnvironmentObject var gameManager: GameManagerVM var body: some View { VStack(spacing: 50) { VStack { Text("\(gameManager.index + 1) out of \(gameManager.lenght)") .foregroundColor(.white) .fontWeight(.heavy) ProgressBar(progress: gameManager.progress) } VStack(alignment: .leading, spacing: 20) { if gameManager.currentCategory == "Sports" { Text(gameManager.question) .font(.system(size: 20)) .bold() .foregroundColor(.black) ForEach(gameManager.answerChoices, id: \.id) { answer in AnswerDisplay(answer: answer) .environmentObject(gameManager) } } } Button { gameManager.nextQuestion() } label: { NextButton(text: "Next") } } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity) .background(LinearGradient(gradient: .init(colors: [.indigo, .white]), startPoint: .zero, endPoint: .bottom)) } }
关键说明
- 用
categoryQuizzes字典存储三个类别的所有题目,避免数据覆盖。 - 通过
switchToCategory方法实现不同类别间的题目切换,重置游戏状态。 - 确保视图中的类别判断与接口返回的
category字段完全匹配(如体育类接口返回"Sports",视图需对应判断该值)。
内容的提问来源于stack exchange,提问作者Lyuba P.
相关产品推荐
相关产品推荐

