WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Day 35 Fatal Error on New View

Forums > 100 Days of SwiftUI

I am working on Day 35, and have figured out how to setup the Questions, etc. When I am trying to show the first question (which is in an array) I get an array index out of order. My (currently incomplete) view is set to just show the first question. I have created a function to run .onAppear and it it shows that I have my 5 questions and successfully prints all five to the console, but the view still crashes.

struct GameView: View {
    var problems: [Problem]
    @State private var score = 0
    @State private var currentQuestion = 0

    var body: some View {
        ZStack {
            Color.yellow
                .ignoresSafeArea()
            VStack {
                Text("Times Table")
                    .font(.title)
                HStack {
                    Text("Question \(currentQuestion)/\(problems.count)")
                    Spacer()
                    Text("Score: \(score)")
                }
                .padding(10)
                Text("\(problems[currentQuestion].question)")
                Spacer()
            }
        }
        .onAppear(perform: printIt)
    }

    func printIt() {
        print("Number of questions = \(problems.count)")
            problems.forEach() { problem in
                print(problem.question)
            }
    }
}

And the console error is

Number of questions = 5
4 x 11 = 
9 x 2 = 
3 x 4 = 
12 x 4 = 
9 x 6 = 
Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range

I am not sure why I get the array index when I initialize currentQuestion to 0. I must be missing something obvious.

2      

The problem count is 5 and currentQuestion is set to 0. The forEach is trying to get to 5 using 0, 1, 2, 3, 4, 5 which is 6 questions. Initializing currentQuestion to 1 should fix your problem.

The way I understood the challenge is you get questions about the number the user picks. So if the choice was 7 the questions should be

4 x 7
9 x 7
3 x 7
12 x 7
9 x 7

your questions seem to be random on both sides. Hope that helps

2      

Thanks! I decided to do Random on both side to make the quiz harder :)

However, since I am only trying to show the first question, (not yet looping thru), changing currentQuestion to 1 still caused the crash. Will keep poking around. and will post if I figure it out.

I did a po currentQuestion and I am getting the following, does it not get initialized?

(lldb) po currentQuestion
error: expression failed to parse:
error: Couldn't lookup symbols:
  TimesTables.GameView.currentQuestion.getter : Swift.Int

2      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Looking in the debug console.. and entering po problems (which is the array used in the .onAppear) I am getting:

(lldb) po problems
0 elements

This is confusing because the printIt function used in .onAppear prints the problems array fine (as shown above). Is there some kind of view Initialization problem here I am not understanding?

2      

Well sorry about that! At 7:00am it looked like a simple problem lol. You didn't post the Problem struct, but i assumed question is a string array. Try this and see if this works.

import SwiftUI

struct ContentView: View {
  var problems: Problem
    @State private var score = 0
    @State private var currentQuestion = 0

    var body: some View {
      ZStack {
        Color.yellow
          .ignoresSafeArea()
        VStack {
          Text("Times Table")
          .font(.title)
          HStack {
            Text("Question \(currentQuestion)/\(problems.question.count)")
            Spacer()
            Text("Score: \(score)")
          }
          .padding(10)
          Text("\(problems.question[currentQuestion])")
          Spacer()
        }
      }
      .onAppear(perform: printIt)
    }
    func printIt() {
     print("Number of questions = \(problems.question.count)")
     problems.question.forEach() { problem in
       print(problem)
     }
   }
}
struct Problem {
  let question = ["4 x 6", "4 x 7", "4 x 8", "5 x 2", "7 x 4"]

}
struct ContentView_Previews: PreviewProvider {
  static private var problems = Problem()
    static var previews: some View {
      ContentView(problems: problems)
    }
}

Hope this time it actually helps lol

2      

I basically just copied your code and pasted it into a new XCode project. I had to change the name of the view to ContentView and create my own Problem struct and problems array, and it seems to be working just fine. So, there doesn't seem to be a problem with the code that you have shown.

But, you don't show how your Problem struct is created or how your problems array is being passed into the view. So, I assume the problem is coming from one of those two places.

struct ContentView: View {
    struct Problem {
        let question: String
        let answer: Int
    }

    @State private var score = 0
    @State private var currentQuestion = 0

    let problems: [Problem] = [
        Problem(question: "5 x 5", answer: 25),
        Problem(question: "10 x 10", answer: 100),
        Problem(question: "2 x 2", answer: 4),
        Problem(question: "2 x 4", answer: 8),
        Problem(question: "0 x 0", answer: 0)
    ]

    var body: some View {
        ZStack {
            Color.yellow
                .ignoresSafeArea()
            VStack {
                Text("Times Table")
                    .font(.title)
                HStack {
                    Text("Question \(currentQuestion)/\(problems.count)")
                    Spacer()
                    Text("Score: \(score)")
                }
                .padding(10)
                Text("\(problems[currentQuestion].question)")
                Spacer()
            }
        }
        .onAppear(perform: printIt)
    }

    func printIt() {
        print("Number of questions = \(problems.count)")
            problems.forEach() { problem in
                print(problem.question)
            }
    }
}

2      

Sorry about that.. Forgot to include Problem Struct (it was in my main ContentView)... (I also just created a GitRepo - https://github.com/TheApApp/TimesTable/tree/main to make life easier)


struct Problem {
    var a = 0
    var b = 0

    var answer: Int {
        a * b
    }

    var question: String {
        "\(a) x \(b) = "
    }
}

I do believe the problem has to be on how I pass from ContentView to GameView. I thought I could just define

var problems: [Problem]

in GameView

and when I am loading GameView, I would pass in the Array like this

GameView(problems: problems)

2      

Things can get a bit complicated when trying to pass an array between views and modify it in SwiftUI. But, you will learn the best ways to do that later on in this course.

For now, I would move the code for creating your problems array into your GameView since that is the place that the array will be used. That will require that you pass the selected settings (difficulty and number of questions) to your GameView though, so that it will have enough information to be able to create the array of problems on its own.

2      

Thanks @Fly0strich, tried to do that, still crashing on the same line... I still think it has something to do with sequence of loading view. Current full code in gitrepo

2      

onAppear is called when the View appears on screen. You are generating the questions at that time.

But the initial render of the body happens before the onAppear triggers, at which time your problems array is empty, so accessing index 0 causes a crash.

If you change to something like this instead, your problem should go away:

first example removed

//1. remove default values from properties
let chosenDifficulty: Int
let times: Int

//2. create an explicit init for the View
init(chosenDifficulty: Int = 0, times: Int = 2) {
    self.chosenDifficulty = chosenDifficulty
    self.times = times

    _problems = State(wrappedValue: generateProblems())
}

//3. remove the onAppear handler

//4. tweak the generateProblems function
func generateProblems() -> [Problem] {
    var p: [Problem] = []
    let numberOfQuestions = [5, 10, 20]
    let number = numberOfQuestions[chosenDifficulty]
    for _ in 0..<number {
        p.append(Problem(a: Int.random(in: 2...self.times), b: Int.random(in: 2...self.times)))
    }
    return p
}

2      

Thank you @roosterboy ... I knew it had to be a sequencing issue... just not yet covered in the course...

2      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.