UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 35 Challenge Fatal Error

Forums > 100 Days of SwiftUI

I've been trying to get this to work, but I'm gettting a fatal error for this code and I have no idea why. Everything seems to be working until I try to reference one of the items stored in the questionSet array. If I run a print() in the startGame function I can get the values I've just added to the array. The array returns the proper number of elements (questionSet.count), but trying to actually output the text of the first question (or any question or answer) gives the following error:

Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444 2020-09-01 08:45:55.750022-0600 Multiplicity[30807:6171277] Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444 (lldb)

Here's the code:

import SwiftUI

struct GameScreen: View {
    @EnvironmentObject var state: GameState

    @State private var questionNumber = 0
    @State private var questionSet: Array<Question> = []

    let test = [Question(text: "What is 6 x 7?", answer: 42)]

    var body: some View {
        VStack {
            Text("Array : \(questionSet.count)")

            Text("\(questionSet[questionNumber].text)")

            Button("End Game") {
                state.active.toggle()
            }
        }
        .onAppear(perform: startGame)
    }

    func setNumberOfQuestions(_ questionIndex: Int) -> Int {
        switch questionIndex {
        case 0:
            return 5
        case 1:
            return 10
        case 2:
            return 20
        default:
            return 12
        }
    }

    func setDifficulty(_ difficultyIndex: Int) -> Int {
        switch difficultyIndex {
        case 0:
            return 4
        case 1:
            return 8
        default:
            return 12
        }
    }

    func startGame() {
        questionSet.removeAll()

        let difficultyRange = setDifficulty(state.selectedDifficulty)
        let numberOfQuestions = setNumberOfQuestions(state.selectedNumberOfQuestions)
        let firstNumber = state.selectedTable
        let secondNumberRange = 0...difficultyRange

        for _ in 0..<numberOfQuestions {
            let secondNumber = secondNumberRange.randomElement() ?? 2
            let question = "What is \(firstNumber) ⨉ \(secondNumber)?"
            let answer = firstNumber * secondNumber
            questionSet.append(Question(text: question, answer: answer))
        }
    }
}

struct Question {
    var text: String
    var answer: Int
}

struct GameScreen_Previews: PreviewProvider {
    static var previews: some View {
        GameScreen().environmentObject(GameState())
    }
}

Any ideas what's going wrong here? I've been trying to figure it out for a day and a half, but I don't see the problem.

2      

I figured it out. The array needed at least one value in it at the start. The startGame function clears the array and repopulates, that function isn't getting called until after the UI is built. I thought it was getting called when the VStack appears, but the I guess the VStack doesn't appear unitl after it tries to lay out all of the elements inside it.

So changing this:

@State private var questionSet: Array<Question> = []

to this:

@State private var questionSet = [Question(text: "What is 6 x 7?", answer: 42)]

makes the whole thing work. That initial value is never even seen, but there just needed to be something there while it built the layout in the first pass.

2      

Well done for finding a solution. You could have done in the body

if questionSet.isEmpty {
  Text("Loading")
} else {
  VStack {
    Text("Array : \(questionSet.count)")

    Text("\(questionSet[questionNumber].text)")

    Button("End Game") {
        state.active.toggle()
    }
  }
}

Even though in your it may not show the "loading" if you were to do something that take longer (EG a network call) to fill Array it would and gives the user that something happening.

PS you need Xcode 12 to use this or put all in Group

2      

Good call. A true loading state would be a great addition. I'm running the betas while I go through the tutorials so I'm all set. Thanks.

2      

You might want to look at How to show progress on a task using ProgressView instead of Text("Loading")

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.