0

I'm new to SwiftUI and trying to build a simple single-player Quiz app. I started by following this tutorial and then tried to continue on my own. Currently, the questions are hardcoded in my ViewModel but I'd like to change it to fetching from a local JSON file instead. Anyone that can point me in the right direction?

Here's a part of my ViewModel with the questions as static data:

extension GameManagerVM {
    static var questions = quizData.shuffled()
    static var quizData: [QuizModel] {
        [
            QuizModel(
                      id: 1,
                      question: "Question title 1",
                      category: "Sport",
                      answer: "A",
                      options: [
                          QuizOption(id: 11, optionId: "A", option: "A"),
                          QuizOption(id: 12, optionId: "B", option: "B"),
                          QuizOption(id: 13, optionId: "C", option: "C"),
                          QuizOption(id: 14, optionId: "D", option: "D")
                      ]
            ),

            ...
    }
}

Here's a test I did, but I get the error that Xcode can't decode it. I replace the above code with this:

extension GameManagerVM {
    static var questions = quizData.shuffled()
    static var quizData: [QuizModel] = Bundle.main.decode("quizquestions2022.json")
}

And here's the JSON.

[
    {
        "id": "1",
        "question": "Question title 1",
        "category": "Sport",
        "answer": "A",
        "options": [
            {
                "id": "1001",
                "optionId": "A", 
                "option": "A"
            },
            {  
                "id": "1002",
                "optionId": "B", 
                "option": "B"
            },
            {
                "id": "1003",
                "optionId": "C", 
                "option": "C"
            },
            {
                "id": "1004",
                "optionId": "D", 
                "option": "D"
            }
        ]
    },
]

Here are my models

struct Quiz {
    var currentQuestionIndex: Int
    var quizModel: QuizModel
    var quizCompleted: Bool = false
    var quizWinningStatus: Bool = false
    var score: Int = 0
}

struct QuizModel: Identifiable, Codable {
    var id: Int
    var question: String
    var category: String
    var answer: String
    var options: [QuizOption]
}

struct QuizOption: Identifiable, Codable {
    var id: Int
    var optionId: String
    var option: String
    var isSelected: Bool = false
    var isMatched: Bool = false
}
ttmoe
  • 15
  • 2

2 Answers2

0

When you are decoding, unless you make your own decoding like this sample init from decoder Then all of non-optional the vars or lets in the struct need to be in the json. Your data doesn't have isSelected or isMatched in the options, so those need to be optional

struct QuizOption: Identifiable, Codable {
    var id: Int
    var optionId: String
    var option: String
    var isSelected: Bool?
    var isMatched: Bool?
HalR
  • 11,411
  • 5
  • 48
  • 80
  • Oh, Thank you! I added the isSelected and isMatched variables to each option in the JSON file and now it works. But what I don't understand is why it worked with the static data where the options didn't have those variables set? – ttmoe Jan 06 '22 at 22:34
  • Putting in your default values (Bool = false) in there makes it so static data would not need to include isSelected or isMatched. The decoder doesn't acknowledge those. – HalR Jan 06 '22 at 23:03
0

to fetch your data from a local JSON file, you could try this approach, where you need to have a model (QuizModel) to match the json data in your file. Also I used a class GameManagerVM: ObservableObject to hold/publish your data as an example:

 import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
 
struct ContentView: View {
    @StateObject var gameManager = GameManagerVM()
    
    var body: some View {
        List {
            ForEach(gameManager.quizData) { quiz in
                Text(quiz.question)
            }
        }
    }
}

struct QuizModel: Identifiable, Codable {
    let id, question, category, answer: String
    let options: [QuizOption]
    
    enum CodingKeys: String, CodingKey {
        case id, question, category, answer, options
    }
}


struct QuizOption: Codable {
    let id, optionId, option: String
    
    var isSelected: Bool = false
    var isMatched: Bool = false
    
    enum CodingKeys: String, CodingKey {
        case id, optionId, option
    }
}

class GameManagerVM: ObservableObject {
    @Published var questions: [QuizModel] = []
    @Published var quizData: [QuizModel] = []
    
    init() {
       quizData = Bundle.main.decode("quizquestions2022.json")
       questions = quizData.shuffled()
    }

}