BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: How do I access to the item I want from an array in a json file?

Forums > SwiftUI

I'm currently working on my project that has a quiz section and its function is similar to "Guss the Flag" but mine doesn't work as how it does. The thing is that you tap the speaker and it plays a sound of a japanese letter then you choose the button that has the correct letter out of three. the letters are shown randomly and it seems like working but when you even click the correct answer, it doesn't show that is correct.

here's the code:

import SwiftUI

struct HiraganaQuiz: View { let hiraganas: [Japanese] = Bundle.main.decode("Hiragana.json") @State private var randomHiraganas: [Japanese]

init() {
    randomHiraganas = hiraganas.shuffled()
}

@State private var correctAnswer = Int.random(in: 0...2)
@StateObject var soundplayer = SoundPlayer()
@State private var answer = ""
@State private var counter = 0
@State private var correctAnswerCounter = 0
@State private var showingAlert = false
@State private var alertMessage = ""
@State private var disabled = false

var body: some View {
    ZStack {
        Color.darkBackground
            .ignoresSafeArea()
        VStack {
            Text("\(counter) / 10")
                .padding(.top,40)
                .foregroundColor(.white)
                .font(.system(size:30).bold())

            Text("Tap the speaker and chose the right letter")
                .foregroundColor(.white)
                .font(.system(size: 20))
                .font(.title)
                .padding(.bottom,80)
                .padding(.top,40)

            Button {
                soundplayer.playSound(file: randomHiraganas[correctAnswer].voice1)
            } label: {
                Image(systemName: "speaker.wave.3.fill")
            }
            .font(.system(size:70))
            .foregroundColor(.blue)
            .frame(width: 110, height: 110)
            .background(.white)
            .cornerRadius(10)
            .padding(.bottom,100)

            HStack {
                ForEach(randomHiraganas[0...2], id: \.id) { aiueo in
                    Button {
                        letterTapped(aiueo.id)
                    } label: {
                        Text(aiueo.letter)
                    }
            }
                 .foregroundColor(.white)
                 .font(.system(size: 35).bold())
                 .frame(width: 70, height: 70)
                 .background(.cyan)
                 .cornerRadius(10)
                 .padding([.leading,.trailing])
            }
            Spacer()
            Spacer()

            Text("\(answer)")
                .foregroundColor(.white)
                .padding(.bottom,20)
                .font(.system(size: 30))

            Button {
                resetTheGame()
            } label: {
                Text("Next")
            }.buttonStyle(.plain)
             .font(.system(size: 30).bold())
             .frame(width: 200, height: 50)
             .foregroundColor(.white)
             .background(.orange)
             .cornerRadius(20)
             .padding([.bottom,.top])

        }
    }
    .alert("⭐️ Well done ⭐️", isPresented: $showingAlert) {
        Button("Retry", action: resetTheGame)

    } message: {
        Text(alertMessage)
    }
}

func letterTapped(_ aiueo: Int) {
    if aiueo == correctAnswer {
        answer = "✨Correct✨"
        correctAnswerCounter += 1
    } else {
        answer = "Incorrect...the answer is '\(randomHiraganas[correctAnswer].letter)'"
    }
    counter += 1

    if counter == 11 {
        counter = 0
        showingAlert = true
        alertMessage = "You got \(correctAnswerCounter) out of 10!"
    }
}

 func resetTheGame() {
    randomHiraganas.shuffle()
    correctAnswer = Int.random(in: 0...2)
    answer = ""
    correctAnswerCounter = 0
}

}

struct HiraganaQuiz_Previews: PreviewProvider { static var previews: some View { HiraganaQuiz() } }

   

this is the DecodeManager:

import Foundation

struct Japanese: Codable, Identifiable { let id: Int let letter: String let image: String let example: String let voice1: String let voice2: String

}

extension Bundle { func decode<T: Codable>(_ file: String) -> T { guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate (file) in bundle.") }

    guard let data = try? Data(contentsOf: url) else {
        fatalError("Failed to load \(file) from bundle.")
    }

    let decoder = JSONDecoder()

    guard let loadedData = try? decoder.decode(T.self, from: data) else {
        fatalError("Failed to decode \(file) from bundle.")
    }
    return loadedData
}

}

   

I think you need to replace T with [T] in: (1) the result type of func decode() (2) try? decoder.decode([T].self

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

thank you for your response :) I replaced them with what you told me, but it did nothing to solve my problem...

   

@Bnerd  

You mean you get the alert that your response is wrong correct? Or you don't get an alert at all? Moreover, how does your JSON look like?

   

I was focussing on the json decoder, but other parts of your code probably need to be debugged too.

For example, in the ForEach, why do you refer to the id instead of the index? The correctAnswer var holds the index, so that's probably what you should be using in the ForEach and the letterTapped() func: ForEach (0...2, id: .self) { index in

I suggest putting a breakpoint at the first statement in func letterTapped(). When it stops at the breakpoint, inspect the values of aiueo and correctAnswer.

Also put a breakpoint at the last statement of extension Bundle { func decode() and inspect whether loadedData is an array of elements of type Japanese whose properties have the expected values. A print statement before the breakpoint would be helpful.

   

@Bnerd

I just want my code to show that is correct when you tap the correct answer, but even when i correct the right one, it shows as it is incorrect.

this is my json file: [ { "id": 0, "letter": "あ", "image": "candies", "example": "あめ", "voice1": "A.mp3", "voice2": "ame.mp3" }, { "id": 1, "letter": "い", "image": "dogs", "example": "いぬ", "voice1": "I.mp3", "voice2": "inu.mp3" }, { "id": 2, "letter": "う", "image": "rabbit", "example": "うさぎ", "voice1": "U.mp3", "voice2": "usagi.mp3" }, { "id": 3, "letter": "え", "image": "shrimp", "example": "えび", "voice1": "E.mp3", "voice2": "ebi.mp3" }, { "id": 4, "letter": "お", "image": "ax", "example": "おの", "voice1": "O.mp3", "voice2": "ono.mp3" }, { "id": 5, "letter": "か", "image": "shell", "example": "かい", "voice1": "ka.mp3", "voice2": "kai.mp3" }, { "id": 6, "letter": "き", "image": "kimono", "example": "きもの", "voice1": "ki.mp3", "voice2": "kimonoS.mp3" }, { "id": 7, "letter": "く", "image": "bear", "example": "くま", "voice1": "ku.mp3", "voice2": "kuma.mp3" }, { "id": 8, "letter": "け", "image": "yarn", "example": "けいと", "voice1": "ke.mp3", "voice2": "keito.mp3" }, { "id": 9, "letter": "こ", "image": "rice", "example": "こめ", "voice1": "ko.mp3", "voice2": "kome.mp3" }, { "id": 10, "letter": "さ", "image": "sakura", "example": "さくら", "voice1": "sa.mp3", "voice2": "sakuraS.mp3" }, { "id": 11, "letter": "し", "image": "newspaper", "example": "しんぶん", "voice1": "shi.mp3", "voice2": "shinnbunn.mp3" }, { "id": 12, "letter": "す", "image": "watermelon", "example": "すいか", "voice1": "su.mp3", "voice2": "suika.mp3" }, { "id": 13, "letter": "せ", "image": "laundrymachine", "example": "せんたくき", "voice1": "se.mp3", "voice2": "senntakuki.mp3" }, { "id": 14, "letter": "そ", "image": "sky", "example": "そら", "voice1": "so.mp3", "voice2": "sora.mp3" }, { "id": 15, "letter": "た", "image": "towel", "example": "たおる", "voice1": "ta.mp3", "voice2": "taoru.mp3" }, { "id": 16, "letter": "ち", "image": "map", "example": "ちず", "voice1": "chi.mp3", "voice2": "chizu.mp3" }, { "id": 17, "letter": "つ", "image": "table", "example": "つくえ", "voice1": "tsu.mp3", "voice2": "tsukue.mp3" }, { "id": 18, "letter": "て", "image": "angel", "example": "てんし", "voice1": "te.mp3", "voice2": "tennshi.mp3" }, { "id": 19, "letter": "と", "image": "tomato", "example": "とまと", "voice1": "to.mp3", "voice2": "tomatoS.mp3" }, { "id": 20, "letter": "な", "image": "pot", "example": "なべ", "voice1": "na.mp3", "voice2": "nabe.mp3" }, { "id": 21, "letter": "に", "image": "meat", "example": "にく", "voice1": "ni.mp3", "voice2": "niku.mp3" }, { "id": 22, "letter": "ぬ", "image": "plushtoy", "example": "ぬいぐるみ", "voice1": "nu.mp3", "voice2": "nuigurumi.mp3" }, { "id": 23, "letter": "ね", "image": "cat", "example": "ねこ", "voice1": "ne.mp3", "voice2": "neko.mp3" }, { "id": 24, "letter": "の", "image": "glue", "example": "のり", "voice1": "no.mp3", "voice2": "nori.mp3" }, { "id": 25, "letter": "は", "image": "toothbrush", "example": "はぶらし", "voice1": "ha.mp3", "voice2": "haburashi.mp3" }, { "id": 26, "letter": "ひ", "image": "ducks", "example": "ひよこ", "voice1": "hi.mp3", "voice2": "hiyoko.mp3" }, { "id": 27, "letter": "ふ", "image": "ship", "example": "ふね", "voice1": "fu.mp3", "voice2": "fune.mp3" }, { "id": 28, "letter": "へ", "image": "snake", "example": "へび", "voice1": "he.mp3", "voice2": "hebi.mp3" }, { "id": 29, "letter": "ほ", "image": "broom", "example": "ほうき", "voice1": "ho.mp3", "voice2": "houki.mp3" }, { "id": 30, "letter": "ま", "image": "window", "example": "まど", "voice1": "ma.mp3", "voice2": "mado.mp3" }, { "id": 31, "letter": "み", "image": "water", "example": "みず", "voice1": "mi.mp3", "voice2": "mizu.mp3" }, { "id": 32, "letter": "む", "image": "wheat", "example": "むぎ", "voice1": "mu.mp3", "voice2": "mugi.mp3" }, { "id": 33, "letter": "め", "image": "glasses", "example": "めがね", "voice1": "me.mp3", "voice2": "megane.mp3" }, { "id": 34, "letter": "も", "image": "peach", "example": "もも", "voice1": "mo.mp3", "voice2": "momo.mp3" }, { "id": 35, "letter": "や", "image": "mountain", "example": "やま", "voice1": "ya.mp3", "voice2": "yama.mp3" }, { "id": 36, "letter": "ゆ", "image": "snow", "example": "ゆき", "voice1": "yu.mp3", "voice2": "yuki.mp3" }, { "id": 37, "letter": "よ", "image": "clothes", "example": "ようふく", "voice1": "yo.mp3", "voice2": "youfuku.mp3" }, { "id": 38, "letter": "ら", "image": "lion", "example": "らいおん", "voice1": "ra.mp3", "voice2": "raionn.mp3" }, { "id": 39, "letter": "り", "image": "squarrel", "example": "りす", "voice1": "ri.mp3", "voice2": "risu.mp3" }, { "id": 40, "letter": "る", "image": "ruby", "example": "るびー", "voice1": "ru.mp3", "voice2": "rubii.mp3" }, { "id": 41, "letter": "れ", "image": "fridge", "example": "れいぞうこ", "voice1": "re.mp3", "voice2": "reizouko.mp3" }, { "id": 42, "letter": "ろ", "image": "candle", "example": "ろうそく", "voice1": "ro.mp3", "voice2": "rousoku.mp3" }, { "id": 43, "letter": "わ", "image": "cottoncandy", "example": "わたあめ", "voice1": "wa.mp3", "voice2": "wataame.mp3" }, { "id": 44, "letter": "を", "image": "drawing", "example": "えをかく(drawing picture)", "voice1": "wo.mp3", "voice2": "ewokaku.mp3" }, { "id": 45, "letter": "ん", "image": "gohann", "example": "ごはん", "voice1": "nn.mp3", "voice2": "gohannVoice.mp3" } ]

   

@bobstern

i used the id because that way code compiled but then when I tried the ForEach(0...2, id: .self), it gets errors like these:

On the ForEach line:

Cannot convert value of type 'ClosedRange<Int>' to expected argument type 'Binding<C>' Generic parameter 'C' could not be inferred

On the Text label line:

Initializer 'init(_:)' requires that 'Binding<Subject>' conform to 'StringProtocol'

   

Oops — I omitted the backslash in the id parameter of ForEach.

Without fully understanding your program, I think this is what you want:

ForEach (0...2, id: \.self) { index in
        Button { letterTapped(index) }  
                     label: { Text(randomHiraganas[index].letter) }
       }

If your label is only text, Apple's reference for Button shows a simpler form of Button with a string parameter instead of a Label:

Button (randomHiraganas[index].letter) { letterTapped(index) } 

   

I think one oversight in the 100 Days course is that it does not suggest how to debug a program. If a program doesn’t work, it's almost impossible to find the problem without inserting several print commands or breakpoints to see what's happening in different parts of the program.

   

@Bnerd  

Bob's answer is correct, I think you need to change the below to

ForEach(0..<3, id: \.self) { aiueo in
                    Button {
                        letterTapped(aiueo) // No need the id anymore, the index will do the same.
                    } label: {
                        Text(aiueo.letter)
                    }
            }

   

But if the ForEach is looping through 0..<3, then aiueo will be an Int, which does not have a letter property.

   

@Bnerd  

You are correct, small correction as per below

ForEach(0..<3, id: \.self) { aiueo in
                    Button {
                        letterTapped(randomHiraganas[aiueo]) // No need the id anymore, the index will do the same.
                    } label: {
                        Text(randomHiraganas[aiueo].letter)
                    }
            }

   

I'm sorry for not being able to giving you a precise explanation but thank you @bobstern @Bnerd @roosterboy for helping me, it worked perfectly :)

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.