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

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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 12:16:59