I'm working on a SwiftUI
app which should load questions from a JSON-file and show them to the user one-by-one. I followed a tutorial on how to load JSON-data
into the view but it's not working. The JSONDecoder
seems to fail, anyone got an idea why?
Here's my code:
CodableBundleExtension.swift
import Foundation
extension Bundle {
func decode(_ file: String) -> [Question] {
// Locate the JSON-file.
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
// Create a property for the data
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
// Create a decoder
let decoder = JSONDecoder()
// Create a property for the decoded data
guard let loaded = try? decoder.decode([Question].self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
// Return the decoded data
return loaded
}
}
Questions.swift
import SwiftUI
struct Question: Codable, Identifiable {
let id: Int
let question: String
let gamemode: Int
let min_players: Int
let max_platers: Int
let question_type: Int
let language: String
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
let questions : [Question] = Bundle.main.decode("questions.json")
ForEach(questions) { item in
Text(item.question)
}
ZStack {
LinearGradient(
gradient: Gradient(
colors: [.blue, .white]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
VStack {
Text("The Questions App")
.font(.system(size: 36))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(.top, 40)
.padding(.bottom, 40)
VStack {
Image(systemName: "homepod")
.renderingMode(.original)
.resizable()
.frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, height: 140, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
.aspectRatio(contentMode: .fit)
Spacer()
Button {
print("tapped")
} label: {
Text("Click Me")
.font(.system(size: 32))
.fontWeight(.medium)
.foregroundColor(.blue)
.frame(width: 200, height: 70, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
.background(Color.white)
.cornerRadius(20)
}
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
questions.json
[
{
"id": 1,
"question": "Testquestion 1",
"gamemode": 1,
"min_players": 2,
"max_players": 4,
"question_type": 1,
"language": "NL"
},
{
"id": 2,
"question": "Testquestion 2",
"gamemode": 1,
"min_players": 2,
"max_players": 99,
"question_type": 2,
"language": "NL"
}
]
And here's the error:
Fatal error: Failed to decode questions.json from bundle.: file Drinking_App_V1/CodableBundleExtension.swift, line 27
2021-04-25 20:19:29.617268+0200 Questions App V1[13218:851770] Fatal error: Failed to decode questions.json from bundle.: file Questions_App_V1/CodableBundleExtension.swift, line 27
(lldb)
If you use do/catch:
extension Bundle {
func decode(_ file: String) -> [Question] {
// Locate the JSON-file.
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
return try decoder.decode([Question].self, from: data)
} catch {
print(error)
fatalError("Failed to decode \(file) from bundle.")
}
}
}
You'll see this error:
keyNotFound(CodingKeys(stringValue: "max_platers", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"max_platers\", intValue: nil) (\"max_platers\").", underlyingError: nil))
This is telling you that there's no value in the JSON for max_platers
, which as you can see, is a misspelling of max_players
.
Convert your model to:
struct Question: Codable, Identifiable {
let id: Int
let question: String
let gamemode: Int
let min_players: Int
let max_players: Int //<-- Here
let question_type: Int
let language: String
}