无法加载问题。SwiftUI

huangapple go评论54阅读模式
英文:

I can't load the questions. SwiftUI

问题

Here's the translated part of your code:

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")

                            }

                            NavigationLink {
                                GeographyQuizView()
                                    .navigationBarHidden(true)
                                    .environmentObject(gameManager)

                            }label: {
                                CategoryButton(text: "Geography")

                            }
                            NavigationLink {
                                SportQuizView()
                                    .navigationBarHidden(true)
                                    .environmentObject(gameManager)

                            }label: {
                                CategoryButton(text: "Sport")

                            }

                        }

                    }
                    .frame(width: 350, height: 400)
                    .background(.ultraThinMaterial)
                    .cornerRadius(40)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewInterfaceOrientation(.portrait)
    }
}

Please let me know if you need further assistance or if you'd like me to translate another part of your code.

英文:

Good day. I am trying to create a quiz game in SwiftUI following MVVM. The game has three categories of questions (History, Geography and Sports). There are 50 questions in each category.

However, I have a problem with the fact that I cannot upload questions from the geography category to GeographyQuizView and also questions from the sport category to SportQuizView. I was able to load only questions from the history category into HistoryQuizView. What is the problem and how to fix it. Thank you.

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")
                                
                            }
                            
                            NavigationLink {
                                GeographyQuizView()
                                    .navigationBarHidden(true)
                                    .environmentObject(gameManager)
                                
                            }label: {
                                CategoryButton(text: "Geography")
                                
                            }
                            NavigationLink {
                                SportQuizView()
                                    .navigationBarHidden(true)
                                    .environmentObject(gameManager)
                                
                            }label: {
                                CategoryButton(text:"Sport")
                                
                            }
                            
                        }
                        
                    }
                    .frame(width: 350, height: 400)
                    .background(.ultraThinMaterial)
                    .cornerRadius(40)
                }
            }
        }
    }
}
                            
                        
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewInterfaceOrientation(.portrait)
    }
}
import SwiftUI

struct GeographyQuizView: 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.category == "Geography"{
                    
                    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))
    }
}

struct GeographyQuizView_Previews: PreviewProvider {
    static var previews: some View {
        GeographyQuizView()
            .environmentObject(GameManagerVM())
    }
}
import SwiftUI


struct HistoryQuizView: 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.category == "History"{
                    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))
    }
}

struct HistoryQuizView_Previews: PreviewProvider {
    static var previews: some View {
        HistoryQuizView()
            .environmentObject(GameManagerVM())
    }
}
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.category == "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))
    }
}

struct SportQuizView_Previews: PreviewProvider {
    static var previews: some View {
        SportQuizView()
            .environmentObject(GameManagerVM())
    }
}
import Foundation
import SwiftUI

class GameManagerVM: ObservableObject {
    private (set)var quiz: [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 category = ""
    
    init() {
        Task.init {
            await fetchQuiz()
        }
    }
    
    func fetchQuiz() async {
        let categoryURLs = [
            "https://opentdb.com/api.php?amount=50&category=21&difficulty=medium&type=multiple",
            "https://opentdb.com/api.php?amount=50&category=22&difficulty=medium&type=multiple",
            "https://opentdb.com/api.php?amount=50&category=23&difficulty=medium&type=multiple"
           
        ]
        
        for urlStr in categoryURLs {
            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.quiz = decodedData.results
                    self.lenght = self.quiz.count
                    
                    if self.quiz.count >= 50 {
                        self.setQuestion()
                    }
                    
                }
            } catch {
                print("Error fetching trivia: \(error)")
            }
        }
    }
    
    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 = quiz[index]
              question = currentTriviaQuestion.formattedQuestion
              answerChoices = currentTriviaQuestion.answers
              category = currentTriviaQuestion.category
            
          }
      }
                      
    
    func selectAnswer(answer: Answer) {
        answerIsSelected = true
        if answer.isCorrect {
            score += 1
        }
    }
}
import SwiftUI

struct CategoryButton: View {
    var text: String
    var background: Color = Color.indigo
    
    var body: some View {
        Text(text)
            .frame(width: 200, height: 80)
            .foregroundColor(.white)
            .background(background)
            .cornerRadius(50)
            .shadow(radius: 20)
            .font(.largeTitle.bold())
            .padding()
            
    }
}

struct CategoryButton_Previews: PreviewProvider {
    static var previews: some View {
        CategoryButton(text: "Category")
    }
}




import SwiftUI

struct AnswerDisplay: View {
    @EnvironmentObject var gameManager: GameManagerVM
    var answer: Answer
    @State private var isSelected = false
    var green = Color.green
    var red = Color.red
    
  
    
    var body: some View {
        HStack(spacing: 20) {
            Image(systemName: "star.fill")
                .foregroundColor(.yellow)
            
            Text(answer.text)
                .bold()
            
                
            
            if isSelected {
                Spacer()
                
                Image(systemName: answer.isCorrect ? "checkmark.circle.fill" : "x.circle.fill")
                    .foregroundColor(answer.isCorrect ? green:red)
            }
            
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .foregroundColor(gameManager.answerIsSelected ? (isSelected ?  Color(.black):.yellow): Color(.black))
        .background(.white)
        .cornerRadius(10)
        .shadow(color: .indigo, radius: 5, x: 0.5, y:0.5)
        .onTapGesture {
            if !gameManager.answerIsSelected {
                isSelected = true
                gameManager.selectAnswer(answer: answer)
            }
            
            
        }
    }
}

struct AnswerDisplay_Previews: PreviewProvider {
    static var previews: some View {
        AnswerDisplay(answer: Answer(text: "Ljuba", isCorrect: false))
            .environmentObject(GameManagerVM())
    }
}




import SwiftUI

struct ProgressBar: View {
    var progress: CGFloat
    
    var body: some View {
        ZStack(alignment: .leading) {
            Rectangle()
                .frame(maxWidth: 350, maxHeight: 4)
                .foregroundColor(Color.gray)
                .cornerRadius(10)
            
            Rectangle()
                .frame(width: min(progress, 350), height: 4)
                .foregroundColor(Color.red)
                .cornerRadius(10)
            
        }
    }
}

struct ProgressBar_Previews: PreviewProvider {
    static var previews: some View {
        ProgressBar(progress: 50)
    }
}





import SwiftUI

struct NextButton: View {
    var text: String
    var body: some View {
        let background: Color = Color.yellow
            Text(text)
                .frame(width: 90, height: 65)
                .foregroundColor(.white)
                .background(background)
                .cornerRadius(20)
                .font(.largeTitle.bold())
                .shadow(radius: 5)
                .padding()
    }
}

struct NextButton_Previews: PreviewProvider {
    static var previews: some View {
        NextButton(text: "Next")
    }
}





import Foundation
import SwiftUI

struct Quiz: Decodable {
    var results: [Result]
    
    
    
    struct Result: Decodable, Identifiable {
        var id: UUID {
            UUID()
        }
        
        var category: String
        var type:String
        var difficulty:String
        var question: String
        var correctAnswer: String
        var incorrectAnswers: [String]
        
        var formattedQuestion: AttributedString {
            do {
                return try AttributedString(markdown: question)
            } catch {
                print("Error setting formattedQuetion: \(error)")
                return ""
            }
        }
        
        var answers: [Answer] {
            do{
                let correct = [Answer(text:try AttributedString(markdown: correctAnswer), isCorrect: true)]
                let incorrects = try incorrectAnswers.map { answer in Answer(text: try AttributedString(markdown: answer), isCorrect: false)
                    
                }
                let allAnswers = correct + incorrects
                
                return allAnswers.shuffled()
            } catch {
                print("Error setting answer")
                return[]
            }
        }
    }
}






import Foundation

struct Answer: Identifiable {
    var id = UUID()
    var text: AttributedString
    var isCorrect: Bool
    
    
}
                 







答案1

得分: 0

欢迎来到 Stack Overflow!您的 GameManagerVM.fetchQuiz() 存在一个缺陷。您正在遍历您的 URLSession 调用,每种类型一个,体育、地理和历史,按顺序进行。每个调用都会发送所请求的数据。然而,您尝试将它们存储在同一个变量 quiz 中。因此,每次获得新数据时,旧数据都会被替换。这发生在您的代码中的这里:

DispatchQueue.main.async {
                        
    self.quiz = decodedData.results // This overwrites the last saved data
    self.lenght = self.quiz.count
                        
    if self.quiz.count >= 50 {
        self.setQuestion()
    }
                        
}

我建议将 quiz 存储为一个 Quiz.Result 字典,而不是数组。它会看起来像 [String:[Quiz.Result]],其中 String 是一个 category。然后,每次下载数据时,您可以将其存储在其自己的类别中。

这会改变上面的部分:

DispatchQueue.main.async {
    // 返回的数据都是同一类别的,所以您可以从第一个元素中提取数据
    // 如果失败,你就没有第一个元素,所以你也不想要数据
    if let category = decodedData.results.first?.category {
        self.quiz[category] = decodedData.results
        self.lenght = self.quiz.count
                            
        ...
    }
}

您可以使用类似这样的简单 UI 来检查是否获得了所有类别和问题:

struct ContentView: View {
    @StateObject var gameManager = GameManagerVM()
    
    var body: some View {
        NavigationView {
            List {
                // 字典是无序的,所以不能直接在 ForEach 中使用
                // 这里,我已将键转换为一个字符串数组
                // 使用 self 作为 id(在这里足够安全,因为类别必须是唯一的)
                ForEach(Array(gameManager.quiz.keys), id:\.self) { category  in
                    Section(category) {
                        // 因为您使用的是字典,通过键访问将返回一个可选值
                        // 但是您知道数据对于键是存在的,否则您就不会有这个键
                        // 因此,去除可选值的最简单方法是与空数组一起使用 nil 合并
                        ForEach(gameManager.quiz[category] ?? []) { quizQuestion in
                            Text(quizQuestion.question)
                        }
                    }
                }
            }
        }
    }
}

我会让您自行更新代码以处理 Dict 而不是 Array。这并不太困难,而且有许多资源可供使用。如果遇到困难,您可以在 Stack Overflow 上提一个新问题。

最后,请尽量清晰地发布问题和显示问题的最小代码。

英文:

Welcome to Stack Overflow! Your GameManagerVM.fetchQuiz() has a flaw in it. You are iterating over your URLSession calls, one for each type, Sports, Geography and History, in that order. Each one of the calls is sending back the requested data. However, you are attempting to store them in the same variable, quiz. So, each time you get new data, the old data is being replaced. This happens here in your code:

DispatchQueue.main.async {
                    
    self.quiz = decodedData.results // This overwrites the last saved data
    self.lenght = self.quiz.count
                    
    if self.quiz.count >= 50 {
        self.setQuestion()
    }
                    
}

I would consider storing quiz not as an array of Quiz.Result, but rather as a dict. It would look like this [String:[Quiz.Result]], where String is a category. Then, each time you download data, you can store it in its own category.

That would change the above section to:

DispatchQueue.main.async {
    // The returned data is all one category, so you can extract the data
    // from the first element. If this fails, you don't have a first element
    // so you got no data, so you don't want it anyway 
    if let category = decodedData.results.first?.category {
        self.quiz[category] = decodedData.results
        self.lenght = self.quiz.count
                        
        ...
    }
}

You can check that you got all your categories and questions with a simple UI like this:

struct ContentView: View {
    @StateObject var gameManager = GameManagerVM()
    
    var body: some View {
        NavigationView {
            List {
                // Dictionaries are unordered, so they can't be used as is in a
                // ForEach. Here, I have turned the keys into an array of
                // String, with an id of self (safe enough here as the
                // categories must be unique.
                ForEach(Array(gameManager.quiz.keys), id:\.self) { category  in
                    Section(category) {
                        // because you are using a dictionary, accessing
                        // by the key will return an optional However,
                        // you know the data exists for the key, else
                        // you wouldn't have the key, so the easiest
                        // way to remove the optional is to nil coalesce
                        // with an empty array
                        ForEach(gameManager.quiz[category] ?? []) { quizQuestion in
                            Text(quizQuestion.question)
                        }
                    }
                }
            }
        }
    }
}

I will let you work through updating the code to handle a Dict instead of an Array. It is not too difficult, and there are many resources. If you get stuck, you can ask a new question on Stack Overflow.

The last point is to please try and post a clear question and the minimal code to show the question.

huangapple
  • 本文由 发表于 2023年6月12日 19:36:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76456276.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定